diff --git a/app/src/main/java/com/depromeet/housekeeper/adapter/AddAssigneeAdapter.kt b/app/src/main/java/com/depromeet/housekeeper/adapter/AddAssigneeAdapter.kt new file mode 100644 index 00000000..f7982b62 --- /dev/null +++ b/app/src/main/java/com/depromeet/housekeeper/adapter/AddAssigneeAdapter.kt @@ -0,0 +1,42 @@ +package com.depromeet.housekeeper.adapter + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.depromeet.housekeeper.databinding.ItemProfileBinding +import com.depromeet.housekeeper.model.Assignee + +class AddAssigneeAdapter(private val assignees: ArrayList) + : RecyclerView.Adapter() { + + @SuppressLint("NotifyDataSetChanged") + fun updateAssignees(assignee: ArrayList) { + assignees.clear() + assignees.addAll(assignee) + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding: ItemProfileBinding = ItemProfileBinding.inflate( + LayoutInflater.from(parent.context), parent, false) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(assignees[position]) + } + + override fun getItemCount(): Int = assignees.size + + inner class ViewHolder(val binding: ItemProfileBinding) + : RecyclerView.ViewHolder(binding.root){ + fun bind(assignee: Assignee) { + binding.assignTemp = assignee + Glide.with(binding.root) + .load(assignee.profilePath) + .into(binding.ivIcon) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/depromeet/housekeeper/adapter/BottomSheetAssigneeAdapter.kt b/app/src/main/java/com/depromeet/housekeeper/adapter/BottomSheetAssigneeAdapter.kt new file mode 100644 index 00000000..5bfe9d10 --- /dev/null +++ b/app/src/main/java/com/depromeet/housekeeper/adapter/BottomSheetAssigneeAdapter.kt @@ -0,0 +1,72 @@ +package com.depromeet.housekeeper.adapter + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.depromeet.housekeeper.databinding.ItemBottomSheetAssigneeBinding +import com.depromeet.housekeeper.model.Assignee +import timber.log.Timber + +class BottomSheetAssigneeAdapter(private val assignees: ArrayList, private val curAssignees: ArrayList) + : RecyclerView.Adapter() { + + private var selectedAssignees: ArrayList = arrayListOf() // 선택한 담당자들 + + init { + selectedAssignees.addAll(curAssignees) + Timber.d(curAssignees.toString()) + Timber.d(selectedAssignees.toString()) + } + + fun getSelectedAssignees(): ArrayList { + return selectedAssignees + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding: ItemBottomSheetAssigneeBinding = ItemBottomSheetAssigneeBinding.inflate( + LayoutInflater.from(parent.context), parent, false) + + binding.itemBottomSheetAssigneeCl.isSelected = false + return ViewHolder(binding) + } + + @SuppressLint("NotifyDataSetChanged") + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(assignees[position]) + + holder.binding.itemBottomSheetAssigneeCl.apply { + setOnClickListener { + isSelected = !isSelected + + when(isSelected) { + true -> selectedAssignees.add(assignees[position]) + false -> selectedAssignees.remove(assignees[position]) + } + } + } + + } + + override fun getItemCount(): Int = assignees.size + + inner class ViewHolder(val binding: ItemBottomSheetAssigneeBinding) + : RecyclerView.ViewHolder(binding.root){ + + @SuppressLint("NotifyDataSetChanged") + fun bind(assignee: Assignee) { + // 초기 selected + if(selectedAssignees.contains(assignee)) { + binding.itemBottomSheetAssigneeCheckIv.isSelected = true + binding.itemBottomSheetAssigneeCl.isSelected = true + } + + binding.itemBottomSheetAssigneeNameTv.text = assignee.memberName + binding.itemBottomSheetAssigneeCl.isSelected = binding.itemBottomSheetAssigneeCheckIv.isSelected + Glide.with(binding.root) + .load(assignee.profilePath) + .into(binding.itemBottomSheetAssigneeProfileIv) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/depromeet/housekeeper/local/PrefsManager.kt b/app/src/main/java/com/depromeet/housekeeper/local/PrefsManager.kt index 5eaa20a5..ffd162b1 100644 --- a/app/src/main/java/com/depromeet/housekeeper/local/PrefsManager.kt +++ b/app/src/main/java/com/depromeet/housekeeper/local/PrefsManager.kt @@ -11,6 +11,8 @@ object PrefsManager { private const val USER_NAME = "USER_NAME" private const val HAS_TEAM = "HAS_TEAM" private const val AUTH_CODE = "AUTH_CODE" + private const val MEMBER_ID = "MEMBER_ID" + fun init(context: Context) { prefs = context.getSharedPreferences("house_keeper", Context.MODE_PRIVATE) @@ -62,4 +64,22 @@ object PrefsManager { putString(AUTH_CODE, authCode) }?.apply() } + + // TODO : memberId 저장 시점 + val memberId: Int + get() = prefs.getInt(MEMBER_ID, 10) + + fun setMemberId(memberId: Int) { + prefs.edit()?.apply { + putInt(MEMBER_ID, memberId) + }?.apply() + } + + fun deleteMemberInfo() { + prefs.edit()?.apply { + remove(MEMBER_ID) + setHasTeam(false) + }?.apply() + } + } diff --git a/app/src/main/java/com/depromeet/housekeeper/model/Assignee.kt b/app/src/main/java/com/depromeet/housekeeper/model/Assignee.kt index c9d2f3f7..efa6c75f 100644 --- a/app/src/main/java/com/depromeet/housekeeper/model/Assignee.kt +++ b/app/src/main/java/com/depromeet/housekeeper/model/Assignee.kt @@ -7,6 +7,6 @@ import kotlinx.parcelize.Parcelize data class Assignee( val memberId: Int, val memberName: String, - val profilePath: String + val profilePath: String, ): Parcelable diff --git a/app/src/main/java/com/depromeet/housekeeper/model/Chore.kt b/app/src/main/java/com/depromeet/housekeeper/model/Chore.kt index cd7c793c..e4cee2e2 100644 --- a/app/src/main/java/com/depromeet/housekeeper/model/Chore.kt +++ b/app/src/main/java/com/depromeet/housekeeper/model/Chore.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable @Serializable data class Chore( - var assignees: List = listOf(0), + var assignees: List = listOf(), var houseWorkName: String = "", var scheduledDate: String = "yyyy-MM-dd", var scheduledTime: String? = null, diff --git a/app/src/main/java/com/depromeet/housekeeper/network/remote/api/ApiService.kt b/app/src/main/java/com/depromeet/housekeeper/network/remote/api/ApiService.kt index 568d7052..8d775b54 100644 --- a/app/src/main/java/com/depromeet/housekeeper/network/remote/api/ApiService.kt +++ b/app/src/main/java/com/depromeet/housekeeper/network/remote/api/ApiService.kt @@ -35,7 +35,7 @@ interface ApiService { ): UpdateChoreResponse @POST("/api/oauth/logout") - suspend fun logout(@Header("Authorization") auth: String) + suspend fun logout() @POST("/api/teams") suspend fun buildTeam(@Body buildTeam: BuildTeam): BuildTeamResponse diff --git a/app/src/main/java/com/depromeet/housekeeper/network/remote/api/RemoteDataSource.kt b/app/src/main/java/com/depromeet/housekeeper/network/remote/api/RemoteDataSource.kt index 5df08169..70280f8c 100644 --- a/app/src/main/java/com/depromeet/housekeeper/network/remote/api/RemoteDataSource.kt +++ b/app/src/main/java/com/depromeet/housekeeper/network/remote/api/RemoteDataSource.kt @@ -18,7 +18,7 @@ interface RemoteDataSource { updateChoreBody: UpdateChoreBody, ): Flow - suspend fun logout(auth: String): Flow + suspend fun logout(): Flow suspend fun buildTeam(buildTeam: BuildTeam): Flow suspend fun getTeam(): Flow suspend fun getProfileImages(): Flow diff --git a/app/src/main/java/com/depromeet/housekeeper/network/remote/model/LoginResponse.kt b/app/src/main/java/com/depromeet/housekeeper/network/remote/model/LoginResponse.kt index 9ff1786e..4c44261b 100644 --- a/app/src/main/java/com/depromeet/housekeeper/network/remote/model/LoginResponse.kt +++ b/app/src/main/java/com/depromeet/housekeeper/network/remote/model/LoginResponse.kt @@ -11,6 +11,7 @@ data class LoginResponse( val hasTeam : Boolean, val isNewMember: Boolean, val memberName: String?, + val memberId: Int, val refreshToken: String, val refreshTokenExpireTime: String ):Parcelable diff --git a/app/src/main/java/com/depromeet/housekeeper/network/remote/repository/Repository.kt b/app/src/main/java/com/depromeet/housekeeper/network/remote/repository/Repository.kt index ee2b1dde..a752c4bf 100644 --- a/app/src/main/java/com/depromeet/housekeeper/network/remote/repository/Repository.kt +++ b/app/src/main/java/com/depromeet/housekeeper/network/remote/repository/Repository.kt @@ -52,9 +52,8 @@ object Repository : RemoteDataSource { } override suspend fun logout( - auth: String, ): Flow = flow { - emit(apiService.logout(auth)) + emit(apiService.logout()) } override suspend fun buildTeam( diff --git a/app/src/main/java/com/depromeet/housekeeper/network/remote/repository/RetrofitBuilder.kt b/app/src/main/java/com/depromeet/housekeeper/network/remote/repository/RetrofitBuilder.kt index c28ab989..7c331919 100644 --- a/app/src/main/java/com/depromeet/housekeeper/network/remote/repository/RetrofitBuilder.kt +++ b/app/src/main/java/com/depromeet/housekeeper/network/remote/repository/RetrofitBuilder.kt @@ -14,6 +14,7 @@ import okhttp3.logging.HttpLoggingInterceptor import okio.IOException import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory +import retrofit2.http.DELETE import java.lang.Exception import java.util.concurrent.TimeUnit @@ -48,12 +49,14 @@ object RetrofitBuilder { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() + return try { chain.proceed( request.newBuilder() - .addHeader("Authorization", PrefsManager.accessToken.ifEmpty { PrefsManager.authCode }) + .addHeader("Authorization", PrefsManager.refreshToken.ifEmpty { PrefsManager.authCode }) .build() ) + } catch (e: Exception) { Response.Builder() .request(request) diff --git a/app/src/main/java/com/depromeet/housekeeper/ui/AddDirectTodoFragment.kt b/app/src/main/java/com/depromeet/housekeeper/ui/AddDirectTodoFragment.kt index 116fbf9a..a2f3e781 100644 --- a/app/src/main/java/com/depromeet/housekeeper/ui/AddDirectTodoFragment.kt +++ b/app/src/main/java/com/depromeet/housekeeper/ui/AddDirectTodoFragment.kt @@ -17,10 +17,13 @@ import androidx.navigation.findNavController import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.depromeet.housekeeper.R +import com.depromeet.housekeeper.adapter.AddAssigneeAdapter import com.depromeet.housekeeper.adapter.DayRepeatAdapter import com.depromeet.housekeeper.databinding.FragmentAddDirectTodoBinding +import com.depromeet.housekeeper.model.Assignee import com.depromeet.housekeeper.model.Chore import com.depromeet.housekeeper.model.enums.ViewType +import com.depromeet.housekeeper.ui.custom.dialog.AssigneeBottomSheetDialog import com.depromeet.housekeeper.ui.custom.dialog.DialogType import com.depromeet.housekeeper.ui.custom.dialog.FairerDialog import com.depromeet.housekeeper.util.spaceNameMapper @@ -32,6 +35,7 @@ class AddDirectTodoFragment : Fragment() { lateinit var binding: FragmentAddDirectTodoBinding lateinit var imm: InputMethodManager lateinit var dayRepeatAdapter: DayRepeatAdapter + lateinit var addAssigneeAdapter: AddAssigneeAdapter private val viewModel: AddDirectTodoViewModel by viewModels() private val navArgs by navArgs() @@ -71,6 +75,13 @@ class AddDirectTodoFragment : Fragment() { onEditView() } } + + lifecycleScope.launchWhenCreated { + viewModel.curAssignees.collect { + addAssigneeAdapter.updateAssignees(it) + } + } + binding.space = spaceNameMapper(viewModel.chores.value[0].space) lifecycleScope.launchWhenStarted { @@ -117,6 +128,10 @@ class AddDirectTodoFragment : Fragment() { } } + binding.addAssigneeLayout.setOnClickListener { + createBottomSheet() + } + binding.addDirectTodoHeader.apply { defaultHeaderBackBtn.setOnClickListener { it.findNavController().navigateUp() @@ -171,6 +186,20 @@ class AddDirectTodoFragment : Fragment() { } } + private fun createBottomSheet() { + val bottomSheet = AssigneeBottomSheetDialog(allGroup = viewModel.allGroupInfo.value, curAssignees = viewModel.curAssignees.value) + bottomSheet.show(childFragmentManager, bottomSheet.tag) + bottomSheet.setMyOkBtnClickListener(object: AssigneeBottomSheetDialog.MyOkBtnClickListener{ + override fun onOkBtnClick() { + viewModel.setCurAssignees(bottomSheet.selectedAssignees) + addAssigneeAdapter.updateAssignees(viewModel.getCurAssignees()) + viewModel.updateAssigneeId() + Timber.d(viewModel.chores.value.toString()) + } + }) + } + + private fun createDatePickerDialog() { val selectDate = navArgs.selectDate.date @@ -195,6 +224,7 @@ class AddDirectTodoFragment : Fragment() { private fun onEditView() { // set arg val houseWork = navArgs.houseWork + Timber.d("TAG $houseWork") val assignees: List = arrayListOf() houseWork.assignees.map { assignees.plus(it.memberId) @@ -202,7 +232,7 @@ class AddDirectTodoFragment : Fragment() { val chore = Chore(assignees, houseWork.houseWorkName, houseWork.scheduledDate, houseWork.scheduledTime, houseWork.space) // viewmodel update - viewModel.initEditChore(chore) + viewModel.initEditChore(chore, houseWork.assignees) viewModel.setHouseWorkId(houseWork.houseWorkId) // ui update @@ -235,6 +265,10 @@ class AddDirectTodoFragment : Fragment() { } private fun setAdapter() { + // 집안일 담당자 adapter + addAssigneeAdapter = AddAssigneeAdapter(viewModel.curAssignees.value) + binding.addAssigneeRv.adapter = addAssigneeAdapter + // 요일 반복 rv adapter val days: Array = resources.getStringArray(R.array.day_array) dayRepeatAdapter = DayRepeatAdapter(days) diff --git a/app/src/main/java/com/depromeet/housekeeper/ui/AddDirectTodoViewModel.kt b/app/src/main/java/com/depromeet/housekeeper/ui/AddDirectTodoViewModel.kt index af739319..710c4d00 100644 --- a/app/src/main/java/com/depromeet/housekeeper/ui/AddDirectTodoViewModel.kt +++ b/app/src/main/java/com/depromeet/housekeeper/ui/AddDirectTodoViewModel.kt @@ -2,6 +2,8 @@ package com.depromeet.housekeeper.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.depromeet.housekeeper.local.PrefsManager +import com.depromeet.housekeeper.model.Assignee import com.depromeet.housekeeper.model.Chore import com.depromeet.housekeeper.model.Chores import com.depromeet.housekeeper.model.enums.ViewType @@ -18,6 +20,11 @@ import java.util.Calendar import java.util.Locale class AddDirectTodoViewModel : ViewModel() { + + init { + setGroupInfo() + } + private val _curViewType: MutableStateFlow = MutableStateFlow(ViewType.ADD) val curViewType: StateFlow @@ -77,6 +84,41 @@ class AddDirectTodoViewModel : ViewModel() { val curSpace: StateFlow get() = _curSpace + val _allGroupInfo: MutableStateFlow> = + MutableStateFlow(arrayListOf()) + val allGroupInfo: StateFlow> + get() = _allGroupInfo + + private fun getMyInfo(): Assignee? { + var temp: Assignee? = null + _allGroupInfo.value.map { + if(it.memberId == PrefsManager.memberId) { + temp = it + } + } + return temp + } + + private val _curAssignees: MutableStateFlow> = + MutableStateFlow(arrayListOf()) + val curAssignees: StateFlow> + get() = _curAssignees + + fun setCurAssignees(assignees: ArrayList) { + _curAssignees.value = assignees + } + + fun getCurAssignees() : ArrayList { + return _curAssignees.value + } + + fun updateAssigneeId() { + val assigneeIds: ArrayList = arrayListOf() + _curAssignees.value.map { + assigneeIds.add(it.memberId) + } + _chores.value[0].assignees = assigneeIds + } // 직접 추가 or 수정은 chore 개수 1 private val _chores: MutableStateFlow> = @@ -85,11 +127,12 @@ class AddDirectTodoViewModel : ViewModel() { get() = _chores fun initDirectChore() { + _chores.value[0].assignees = arrayListOf(PrefsManager.memberId) _chores.value[0].scheduledDate = _curDate.value _chores.value[0].space = Chore.ETC_SPACE } - fun initEditChore(chore: Chore) { + fun initEditChore(chore: Chore, curAssignees: List) { // main에서 받아온 집안일 정보 init _curDate.value = chore.scheduledDate _curTime.value = chore.scheduledTime @@ -99,6 +142,8 @@ class AddDirectTodoViewModel : ViewModel() { _chores.value[0].scheduledTime = chore.scheduledTime _chores.value[0].space = chore.space _chores.value[0].assignees = chore.assignees + + setCurAssignees(curAssignees as ArrayList) } @@ -125,6 +170,22 @@ class AddDirectTodoViewModel : ViewModel() { _selectCalendar.value = SimpleDateFormat(datePattern, Locale.getDefault()).format(calendar.time) } + // TODO: 팀 조회 API에서 members 정보만 GET + private fun setGroupInfo() { + viewModelScope.launch { + Repository.getTeam().runCatching { + collect { + _allGroupInfo.value = it.members as ArrayList + + // 직접 추가 뷰라면 "나" 자신 담당자 추가 + if(_curViewType.value == ViewType.ADD) { + setCurAssignees(arrayListOf(getMyInfo()!!)) + } + } + } + } + } + // 집안일 직접 추가 api fun createHouseWorks() { viewModelScope.launch { diff --git a/app/src/main/java/com/depromeet/housekeeper/ui/AddHouseWorkFramgent.kt b/app/src/main/java/com/depromeet/housekeeper/ui/AddHouseWorkFragment.kt similarity index 61% rename from app/src/main/java/com/depromeet/housekeeper/ui/AddHouseWorkFramgent.kt rename to app/src/main/java/com/depromeet/housekeeper/ui/AddHouseWorkFragment.kt index 7d052196..35dba79e 100644 --- a/app/src/main/java/com/depromeet/housekeeper/ui/AddHouseWorkFramgent.kt +++ b/app/src/main/java/com/depromeet/housekeeper/ui/AddHouseWorkFragment.kt @@ -14,19 +14,23 @@ import androidx.navigation.findNavController import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.depromeet.housekeeper.R +import com.depromeet.housekeeper.adapter.AddAssigneeAdapter import com.depromeet.housekeeper.adapter.AddHouseWorkChoreAdapter import com.depromeet.housekeeper.adapter.DayRepeatAdapter import com.depromeet.housekeeper.databinding.FragmentAddHouseWorkBinding +import com.depromeet.housekeeper.model.Assignee +import com.depromeet.housekeeper.ui.custom.dialog.AssigneeBottomSheetDialog import kotlinx.coroutines.flow.collect import timber.log.Timber import java.util.* -class AddHouseWorkFramgent : Fragment() { +class AddHouseWorkFragment : Fragment() { lateinit var binding: FragmentAddHouseWorkBinding lateinit var dayRepeatAdapter: DayRepeatAdapter lateinit var addHouseWorkChoreAdapter: AddHouseWorkChoreAdapter - private val addTodo2ViewModel: AddHouseWorkViewModel by viewModels() - private val navArgs by navArgs() + lateinit var addAssigneeAdapter: AddAssigneeAdapter + private val viewModel: AddHouseWorkViewModel by viewModels() + private val navArgs by navArgs() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -35,44 +39,44 @@ class AddHouseWorkFramgent : Fragment() { // Inflate the layout for this fragment binding = DataBindingUtil.inflate(inflater, R.layout.fragment_add_house_work, container, false) binding.lifecycleOwner = this.viewLifecycleOwner - binding.vm = addTodo2ViewModel - addTodo2ViewModel.addCalendarView(navArgs.selectDate.date) - binding.currentDate = addTodo2ViewModel.bindingDate() + binding.vm = viewModel + viewModel.addCalendarView(navArgs.selectDate.date) + binding.currentDate = viewModel.bindingDate() return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + setAdapter() bindingVm() initListener() - setAdapter() } private fun bindingVm() { - val choreNames = navArgs.spaceChores.houseWorks - val space = navArgs.spaceChores.spaceName - addTodo2ViewModel.updateSpace(space) - addTodo2ViewModel.setDate(navArgs.selectDate.date) - addTodo2ViewModel.initChores(addTodo2ViewModel.getSpace(), choreNames) - Timber.d(addTodo2ViewModel.getChores().toString()) lifecycleScope.launchWhenStarted { - addTodo2ViewModel.selectCalendar.collect { - binding.addHouseWorkDateTv.text = addTodo2ViewModel.bindingDate() + viewModel.selectCalendar.collect { + binding.addHouseWorkDateTv.text = viewModel.bindingDate() } } lifecycleScope.launchWhenCreated { - addTodo2ViewModel.networkError.collect { + viewModel.networkError.collect { binding.isConnectedNetwork = it } } lifecycleScope.launchWhenCreated { - addTodo2ViewModel.houseWorkCreateResponse.collect { + viewModel.curAssignees.collect { + addAssigneeAdapter.updateAssignees(it) + } + } + + lifecycleScope.launchWhenCreated { + viewModel.houseWorkCreateResponse.collect { if (it?.any { it.success } == false) { // 화면 전환 - findNavController().navigate(R.id.action_addHouseWorkFramgent_to_mainFragment) + findNavController().navigate(R.id.action_addHouseWorkFragment_to_mainFragment) } } } @@ -94,65 +98,57 @@ class AddHouseWorkFramgent : Fragment() { setOnClickListener { // 마지막 position update - updateChore(addTodo2ViewModel.getPosition(PositionType.CUR)) - addTodo2ViewModel.updateChoreDate() + updateChore(viewModel.getPosition(PositionType.CUR)) + viewModel.updateChoreDate() // 집안일 생성 api - addTodo2ViewModel.createHouseWorks() + viewModel.createHouseWorks() } } binding.todoTimePicker.setOnTimeChangedListener { _, _, _ -> binding.addHouseWorkAllDayCheckBox.isChecked = false val time = binding.todoTimePicker.getDisPlayedTime() - addTodo2ViewModel.updateTime(time.first, time.second) + viewModel.updateTime(time.first, time.second) } binding.addHouseWorkAllDayCheckBox.apply { setOnClickListener { val time = binding.todoTimePicker.getDisPlayedTime() - addTodo2ViewModel.updateTime(time.first, time.second) + viewModel.updateTime(time.first, time.second) } } binding.addHouseWorkDateTv.setOnClickListener { createDatePickerDialog() } - } - - private fun createDatePickerDialog() { - val selectDate = navArgs.selectDate.date - val calendar = Calendar.getInstance().apply { - set(Calendar.YEAR, selectDate.split("-")[0].toInt()) - set(Calendar.MONTH, selectDate.split("-")[1].toInt()) - set(Calendar.DAY_OF_MONTH, selectDate.split("-")[2].toInt()) + binding.addAssigneeLayout.setOnClickListener{ + createBottomSheet() } - - val datePickerDialog = DatePickerDialog( - this.requireContext(), - { _, year, month, dayOfMonth -> - addTodo2ViewModel.updateCalendarView(year, month, dayOfMonth) - }, - calendar.get(Calendar.YEAR), - calendar.get(Calendar.MONTH) - 1, - calendar.get(Calendar.DAY_OF_MONTH), - ) - datePickerDialog.show() } - private fun setAdapter() { - // chore list rv adapter - addHouseWorkChoreAdapter = AddHouseWorkChoreAdapter(addTodo2ViewModel.getChores()) + // 집안일 담당자 adapter + addAssigneeAdapter = AddAssigneeAdapter(viewModel.curAssignees.value) + binding.addAssigneeRv.adapter = addAssigneeAdapter + + // 집안일 adapter + val choreNames = navArgs.spaceChores.houseWorks + val space = navArgs.spaceChores.spaceName + viewModel.updateSpace(space) + viewModel.setDate(navArgs.selectDate.date) + + viewModel.initChores(viewModel.getSpace(), choreNames) + addHouseWorkChoreAdapter = AddHouseWorkChoreAdapter(viewModel.getChores()) binding.addHouseWorkChoreListRv.adapter = addHouseWorkChoreAdapter addHouseWorkChoreAdapter.setMyItemClickListener(object : AddHouseWorkChoreAdapter.MyItemClickListener { override fun onItemClick(position: Int) { // 현재 chore 클릭하면 이전 chore 정보 업데이트 - addTodo2ViewModel.updatePositions(position) - val prePos = addTodo2ViewModel.getPosition(PositionType.PRE) + viewModel.updatePositions(position) + val prePos = viewModel.getPosition(PositionType.PRE) updateChore(prePos) // 현재 chore 기준으로 뷰 업데이트 @@ -166,34 +162,33 @@ class AddHouseWorkFramgent : Fragment() { // 현재 select 된 pos 정보 -> select 되지 않아도 remove 가능하기 때문 val selectedPos = addHouseWorkChoreAdapter.selectedChore.indexOf(1) - if (addTodo2ViewModel.getPosition(PositionType.CUR) != selectedPos) { - addTodo2ViewModel.updatePositions(selectedPos) + if (viewModel.getPosition(PositionType.CUR) != selectedPos) { + viewModel.updatePositions(selectedPos) } - updateView(addTodo2ViewModel.getPosition(PositionType.CUR)) + updateView(viewModel.getPosition(PositionType.CUR)) } }) - // 요일 반복 rv adapter + // 요일 반복 adapter val days: Array = resources.getStringArray(R.array.day_array) dayRepeatAdapter = DayRepeatAdapter(days) binding.addHouseWorkRepeatRv.adapter = dayRepeatAdapter - } private fun updateChore(position: Int) { when { - binding.addHouseWorkAllDayCheckBox.isChecked -> addTodo2ViewModel.updateChore( + binding.addHouseWorkAllDayCheckBox.isChecked -> viewModel.updateChore( null, position ) - else -> addTodo2ViewModel.updateChore(addTodo2ViewModel.curTime.value, position) + else -> viewModel.updateChore(viewModel.curTime.value, position) } - Timber.d(addTodo2ViewModel.chores.value.toString()) + Timber.d(viewModel.chores.value.toString()) } private fun updateView(position: Int) { - val chore = addTodo2ViewModel.getChore(position) + val chore = viewModel.getChore(position) if (chore.scheduledTime == null) { binding.todoTimePicker.initDisPlayedValue() binding.addHouseWorkAllDayCheckBox.isChecked = true @@ -201,6 +196,51 @@ class AddHouseWorkFramgent : Fragment() { val time = parseTime(chore.scheduledTime!!) binding.todoTimePicker.setDisPlayedValue(time.first, time.second) } + + val curAssignees = arrayListOf() + viewModel.allGroupInfo.value.map { assignee -> + chore.assignees.map { curId -> + if(assignee.memberId == curId) { + curAssignees.add(assignee) + } + } + } + viewModel.setCurAssignees(curAssignees) + addAssigneeAdapter.updateAssignees(viewModel.getCurAssignees()) + } + + private fun createBottomSheet() { + val bottomSheet = AssigneeBottomSheetDialog(allGroup = viewModel.allGroupInfo.value, curAssignees = viewModel.curAssignees.value) + bottomSheet.show(childFragmentManager, bottomSheet.tag) + bottomSheet.setMyOkBtnClickListener(object: AssigneeBottomSheetDialog.MyOkBtnClickListener{ + override fun onOkBtnClick() { + viewModel.setCurAssignees(bottomSheet.selectedAssignees) + addAssigneeAdapter.updateAssignees(viewModel.getCurAssignees()) + viewModel.updateAssigneeId(position = viewModel.getPosition(PositionType.CUR)) + Timber.d(viewModel.chores.value.toString()) + } + }) + } + + private fun createDatePickerDialog() { + val selectDate = navArgs.selectDate.date + + val calendar = Calendar.getInstance().apply { + set(Calendar.YEAR, selectDate.split("-")[0].toInt()) + set(Calendar.MONTH, selectDate.split("-")[1].toInt()) + set(Calendar.DAY_OF_MONTH, selectDate.split("-")[2].toInt()) + } + + val datePickerDialog = DatePickerDialog( + this.requireContext(), + { _, year, month, dayOfMonth -> + viewModel.updateCalendarView(year, month, dayOfMonth) + }, + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH) - 1, + calendar.get(Calendar.DAY_OF_MONTH), + ) + datePickerDialog.show() } private fun parseTime(time: String): Pair { diff --git a/app/src/main/java/com/depromeet/housekeeper/ui/AddHouseWorkViewModel.kt b/app/src/main/java/com/depromeet/housekeeper/ui/AddHouseWorkViewModel.kt index fc07f654..90e35b9c 100644 --- a/app/src/main/java/com/depromeet/housekeeper/ui/AddHouseWorkViewModel.kt +++ b/app/src/main/java/com/depromeet/housekeeper/ui/AddHouseWorkViewModel.kt @@ -2,6 +2,8 @@ package com.depromeet.housekeeper.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.depromeet.housekeeper.local.PrefsManager +import com.depromeet.housekeeper.model.Assignee import com.depromeet.housekeeper.model.Chore import com.depromeet.housekeeper.model.Chores import com.depromeet.housekeeper.model.HouseWork @@ -15,8 +17,14 @@ import kotlinx.coroutines.launch import timber.log.Timber import java.text.SimpleDateFormat import java.util.* +import kotlin.collections.ArrayList class AddHouseWorkViewModel: ViewModel(){ + + init { + setGroupInfo() + } + private val _curDate: MutableStateFlow = MutableStateFlow("") val curDate: StateFlow @@ -45,6 +53,42 @@ class AddHouseWorkViewModel: ViewModel(){ return _curSpace.value } + val _allGroupInfo: MutableStateFlow> = + MutableStateFlow(arrayListOf()) + val allGroupInfo: StateFlow> + get() = _allGroupInfo + + private fun getMyInfo(): Assignee? { + var temp: Assignee? = null + _allGroupInfo.value.map { + if(it.memberId == PrefsManager.memberId) { + temp = it + } + } + return temp + } + + private val _curAssignees: MutableStateFlow> = + MutableStateFlow(arrayListOf()) + val curAssignees: StateFlow> + get() = _curAssignees + + fun setCurAssignees(assignees: ArrayList) { + _curAssignees.value = assignees + } + + fun getCurAssignees() : ArrayList { + return _curAssignees.value + } + + fun updateAssigneeId(position: Int) { + val assigneeIds: ArrayList = arrayListOf() + _curAssignees.value.map { + assigneeIds.add(it.memberId) + } + _chores.value[position].assignees = assigneeIds + } + private val _curTime: MutableStateFlow = MutableStateFlow(null) val curTime: StateFlow @@ -82,6 +126,7 @@ class AddHouseWorkViewModel: ViewModel(){ chore.scheduledDate = _curDate.value chore.space = space.uppercase() chore.houseWorkName = name + chore.assignees = arrayListOf(PrefsManager.memberId) temp.add(chore) } _chores.value.addAll(temp) @@ -113,6 +158,20 @@ class AddHouseWorkViewModel: ViewModel(){ val houseWorkCreateResponse: StateFlow?> get() = _houseWorkCreateResponse + // TODO: 팀 조회 API에서 members 정보만 GET + private fun setGroupInfo() { + viewModelScope.launch { + Repository.getTeam().runCatching { + collect { + _allGroupInfo.value = it.members as ArrayList + + // 초기에 "나"만 들어가도록 수정 + setCurAssignees(arrayListOf(getMyInfo()!!)) + } + } + } + } + fun createHouseWorks() { viewModelScope.launch { Repository.createHouseWorks(Chores(_chores.value)) diff --git a/app/src/main/java/com/depromeet/housekeeper/ui/LoginFragment.kt b/app/src/main/java/com/depromeet/housekeeper/ui/LoginFragment.kt index db613bf0..279cd067 100644 --- a/app/src/main/java/com/depromeet/housekeeper/ui/LoginFragment.kt +++ b/app/src/main/java/com/depromeet/housekeeper/ui/LoginFragment.kt @@ -116,6 +116,7 @@ class LoginFragment : Fragment() { PrefsManager.setUserName(it) } PrefsManager.setHasTeam(response.hasTeam) + PrefsManager.setMemberId(response.memberId) initNavigation(response) } } diff --git a/app/src/main/java/com/depromeet/housekeeper/ui/SelectSpaceFragment.kt b/app/src/main/java/com/depromeet/housekeeper/ui/SelectSpaceFragment.kt index 65dc9812..f364fda8 100644 --- a/app/src/main/java/com/depromeet/housekeeper/ui/SelectSpaceFragment.kt +++ b/app/src/main/java/com/depromeet/housekeeper/ui/SelectSpaceFragment.kt @@ -165,7 +165,7 @@ class SelectSpaceFragment : Fragment(), View.OnClickListener { } private fun navigateToAddTodoPage2() { - findNavController().navigate(SelectSpaceFragmentDirections.actionSelectSpaceFragmentToAddHouseWorkFramgent( + findNavController().navigate(SelectSpaceFragmentDirections.actionSelectSpaceFragmentToAddHouseWorkFragment( SpaceChores( spaceName = viewModel.selectSpace.value, houseWorks = viewModel.chores.value, diff --git a/app/src/main/java/com/depromeet/housekeeper/ui/SettingViewModel.kt b/app/src/main/java/com/depromeet/housekeeper/ui/SettingViewModel.kt index d42718ce..29cf0b2f 100644 --- a/app/src/main/java/com/depromeet/housekeeper/ui/SettingViewModel.kt +++ b/app/src/main/java/com/depromeet/housekeeper/ui/SettingViewModel.kt @@ -33,7 +33,7 @@ class SettingViewModel : ViewModel() { // logout api private fun logout() { viewModelScope.launch { - Repository.logout(PrefsManager.refreshToken) + Repository.logout() .runCatching { collect { Timber.d(it.toString()) diff --git a/app/src/main/java/com/depromeet/housekeeper/ui/SignProfileFragment.kt b/app/src/main/java/com/depromeet/housekeeper/ui/SignProfileFragment.kt index 08928bf8..9754a5c3 100644 --- a/app/src/main/java/com/depromeet/housekeeper/ui/SignProfileFragment.kt +++ b/app/src/main/java/com/depromeet/housekeeper/ui/SignProfileFragment.kt @@ -1,5 +1,6 @@ package com.depromeet.housekeeper.ui +import android.annotation.SuppressLint import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -47,6 +48,7 @@ class SignProfileFragment : Fragment() { bindingVm() } + @SuppressLint("NotifyDataSetChanged") private fun bindingVm() { viewModel.setViewType(navArgs.viewType) binding.viewType = viewModel.viewType.value diff --git a/app/src/main/java/com/depromeet/housekeeper/ui/custom/dialog/AssigneeBottomSheetDialog.kt b/app/src/main/java/com/depromeet/housekeeper/ui/custom/dialog/AssigneeBottomSheetDialog.kt new file mode 100644 index 00000000..9ffd56ff --- /dev/null +++ b/app/src/main/java/com/depromeet/housekeeper/ui/custom/dialog/AssigneeBottomSheetDialog.kt @@ -0,0 +1,74 @@ +package com.depromeet.housekeeper.ui.custom.dialog + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.databinding.DataBindingUtil +import com.depromeet.housekeeper.R +import com.depromeet.housekeeper.adapter.BottomSheetAssigneeAdapter +import com.depromeet.housekeeper.databinding.FragmentAssigneeBottomSheetDialogBinding +import com.depromeet.housekeeper.model.Assignee +import com.google.android.material.bottomsheet.BottomSheetDialogFragment + +class AssigneeBottomSheetDialog(val allGroup: ArrayList, private val curAssignees: ArrayList) : BottomSheetDialogFragment() { + lateinit var binding: FragmentAssigneeBottomSheetDialogBinding + lateinit var bottomSheetAssigneeAdapter: BottomSheetAssigneeAdapter + private lateinit var mOkBtnClickListener: MyOkBtnClickListener + + interface MyOkBtnClickListener { + fun onOkBtnClick() + } + + fun setMyOkBtnClickListener(okBtnClickListener: MyOkBtnClickListener){ + mOkBtnClickListener = okBtnClickListener + } + + val selectedAssignees:ArrayList = curAssignees + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + // Inflate the layout for this fragment + binding = DataBindingUtil.inflate(inflater, R.layout.fragment_assignee_bottom_sheet_dialog, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setAdapter() + initClickListener() + } + + private fun setAdapter() { + bottomSheetAssigneeAdapter = BottomSheetAssigneeAdapter(allGroup, curAssignees) + binding.bottomSheetAssigneeRv.adapter = bottomSheetAssigneeAdapter + } + + private fun initClickListener() { + binding.bottomSheetDlgCancelBtn.setOnClickListener { + dialog!!.dismiss() + } + + binding.bottomSheetDlgOkBtn.setOnClickListener { + addAssignee() + mOkBtnClickListener.onOkBtnClick() + when { + selectedAssignees.isNotEmpty() -> { + dialog!!.dismiss() + } + else -> { + Toast.makeText(requireContext(), getString(R.string.add_assignee_toast_text), Toast.LENGTH_SHORT).show() + } + } + } + } + + private fun addAssignee() { + selectedAssignees.clear() + selectedAssignees.addAll(bottomSheetAssigneeAdapter.getSelectedAssignees()) + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_assignee_bottom_sheet_dialog.xml b/app/src/main/res/drawable/bg_assignee_bottom_sheet_dialog.xml new file mode 100644 index 00000000..112b86ce --- /dev/null +++ b/app/src/main/res/drawable/bg_assignee_bottom_sheet_dialog.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bottom_sheet_item_assignee_selector.xml b/app/src/main/res/drawable/bottom_sheet_item_assignee_selector.xml new file mode 100644 index 00000000..1ff9b4e3 --- /dev/null +++ b/app/src/main/res/drawable/bottom_sheet_item_assignee_selector.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bottom_sheet_item_check_selector.xml b/app/src/main/res/drawable/bottom_sheet_item_check_selector.xml new file mode 100644 index 00000000..2a39e9aa --- /dev/null +++ b/app/src/main/res/drawable/bottom_sheet_item_check_selector.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_circle_check_active.xml b/app/src/main/res/drawable/ic_circle_check_active.xml new file mode 100644 index 00000000..6f6a83f7 --- /dev/null +++ b/app/src/main/res/drawable/ic_circle_check_active.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_circle_check_inactive.xml b/app/src/main/res/drawable/ic_circle_check_inactive.xml new file mode 100644 index 00000000..ba3b9ce9 --- /dev/null +++ b/app/src/main/res/drawable/ic_circle_check_inactive.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml deleted file mode 100644 index e54c22b4..00000000 --- a/app/src/main/res/drawable/ic_info.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_info_outline.xml b/app/src/main/res/drawable/ic_info_outline.xml index 4380debc..dc5ef10d 100644 --- a/app/src/main/res/drawable/ic_info_outline.xml +++ b/app/src/main/res/drawable/ic_info_outline.xml @@ -1,15 +1,9 @@ + android:width="18dp" + android:height="18dp" + android:viewportWidth="18" + android:viewportHeight="18"> - - diff --git a/app/src/main/res/drawable/ic_plus.xml b/app/src/main/res/drawable/ic_plus.xml index 70046c48..788e8fb9 100644 --- a/app/src/main/res/drawable/ic_plus.xml +++ b/app/src/main/res/drawable/ic_plus.xml @@ -1,5 +1,9 @@ - - + + diff --git a/app/src/main/res/layout/fragment_add_direct_todo.xml b/app/src/main/res/layout/fragment_add_direct_todo.xml index 5034a860..618a9083 100644 --- a/app/src/main/res/layout/fragment_add_direct_todo.xml +++ b/app/src/main/res/layout/fragment_add_direct_todo.xml @@ -115,6 +115,48 @@ android:textColorHint="@color/gray_400" android:textSize="14sp" app:layout_constraintTop_toBottomOf="@id/add_direct_todo_title_sign_tv" /> + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/add_assignee_layout" /> + tools:context=".ui.AddHouseWorkFragment"> - + + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/add_assignee_layout" /> + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_rule.xml b/app/src/main/res/layout/fragment_rule.xml index 6358cd5e..c328776b 100644 --- a/app/src/main/res/layout/fragment_rule.xml +++ b/app/src/main/res/layout/fragment_rule.xml @@ -78,7 +78,7 @@ android:layout_height="wrap_content" android:layout_marginStart="24dp" android:layout_marginTop="16dp" - android:drawableStart="@drawable/ic_info" + android:drawableStart="@drawable/ic_info_fill" android:drawablePadding="8dp" android:text="@string/rule_info" android:textColor="@color/gray_600" diff --git a/app/src/main/res/layout/fragment_select_space.xml b/app/src/main/res/layout/fragment_select_space.xml index c8be1373..6fb6e888 100644 --- a/app/src/main/res/layout/fragment_select_space.xml +++ b/app/src/main/res/layout/fragment_select_space.xml @@ -197,7 +197,7 @@ android:layout_marginTop="12dp" android:textColor="@color/gray_500" android:drawablePadding="5dp" - android:drawableLeft="@drawable/ic_info_outline" + android:drawableLeft="@drawable/ic_info_fill" android:text="@string/add_todo_info" app:layout_constraintStart_toStartOf="@id/select_space_image_entrance" app:layout_constraintTop_toBottomOf="@+id/select_space_image_outside" /> diff --git a/app/src/main/res/layout/fragment_setting.xml b/app/src/main/res/layout/fragment_setting.xml index 1d0b088d..e45a9281 100644 --- a/app/src/main/res/layout/fragment_setting.xml +++ b/app/src/main/res/layout/fragment_setting.xml @@ -101,7 +101,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" - android:drawableStart="@drawable/ic_info" + android:drawableStart="@drawable/ic_info_outline" android:drawablePadding="12dp" android:text="@string/setting_contact_row_text" style="@style/Body.B2" @@ -130,7 +130,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" - android:drawableStart="@drawable/ic_info" + android:drawableStart="@drawable/ic_info_outline" android:drawablePadding="12dp" android:text="@string/setting_info_row_text" style="@style/Body.B2" diff --git a/app/src/main/res/layout/item_add_assignee.xml b/app/src/main/res/layout/item_add_assignee.xml new file mode 100644 index 00000000..d07170db --- /dev/null +++ b/app/src/main/res/layout/item_add_assignee.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_bottom_sheet_assignee.xml b/app/src/main/res/layout/item_bottom_sheet_assignee.xml new file mode 100644 index 00000000..2b850de3 --- /dev/null +++ b/app/src/main/res/layout/item_bottom_sheet_assignee.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_profile.xml b/app/src/main/res/layout/item_profile.xml index 4508d388..45b3e684 100644 --- a/app/src/main/res/layout/item_profile.xml +++ b/app/src/main/res/layout/item_profile.xml @@ -53,6 +53,9 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="8dp" + android:ellipsize="end" + android:maxEms="3" + android:maxLines="1" android:text="@{assignTemp.memberName}" android:textAlignment="center" android:textColor="@color/gray_800" diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 2a4f847d..74356c84 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -63,8 +63,8 @@ android:name="selectDate" app:argType="com.depromeet.housekeeper.model.DayOfWeek" /> 수정 완료 기타 공간 + + 집안일 담당자 + 집안일 담당자 선택 + 취소 + 확인 + 집안일 담당자를 지정해주세요. + 취소 diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index d8a48c19..44202be2 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -13,6 +13,7 @@ @color/white true + @style/FairerBottomSheetDialogTheme @color/highlight @color/highlight @@ -24,6 +25,14 @@ @color/highlight + + + +