diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/Node.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/Node.kt index ee50390c..3076c7d1 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/Node.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/Node.kt @@ -1,14 +1,16 @@ package boostcamp.and07.mindsync.data.model import boostcamp.and07.mindsync.ui.util.Dp +import kotlinx.serialization.Serializable +@Serializable sealed class Node( open val id: String, open val parentId: String?, open val path: NodePath, open val description: String, open val children: List, -) +) : java.io.Serializable data class CircleNode( override val id: String, diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/NodePath.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/NodePath.kt index d37f3323..e6199f7e 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/NodePath.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/NodePath.kt @@ -1,7 +1,9 @@ package boostcamp.and07.mindsync.data.model import boostcamp.and07.mindsync.ui.util.Dp +import kotlinx.serialization.Serializable +@Serializable sealed class NodePath(open val centerX: Dp, open val centerY: Dp) data class RectanglePath( diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/data/network/response/mindmap/SerializedCrdtTree.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/data/network/response/mindmap/SerializedCrdtTree.kt index e8b7f06b..d2f78198 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/data/network/response/mindmap/SerializedCrdtTree.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/data/network/response/mindmap/SerializedCrdtTree.kt @@ -45,5 +45,5 @@ data class NodeDto( @SerialName("parentId") val parentId: String, @SerialName("description") - val description: String, + val description: String = "", ) diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/dialog/EditDescriptionDialog.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/dialog/EditDescriptionDialog.kt index 7240a5f9..4eee8e1b 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/dialog/EditDescriptionDialog.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/dialog/EditDescriptionDialog.kt @@ -10,17 +10,22 @@ import android.view.View import android.view.ViewGroup import android.view.WindowManager import androidx.fragment.app.DialogFragment +import androidx.navigation.fragment.navArgs +import androidx.navigation.navGraphViewModels import boostcamp.and07.mindsync.R +import boostcamp.and07.mindsync.data.NodeGenerator +import boostcamp.and07.mindsync.data.crdt.OperationType +import boostcamp.and07.mindsync.data.model.CircleNode +import boostcamp.and07.mindsync.data.model.RectangleNode import boostcamp.and07.mindsync.databinding.DialogEditDescriptionBinding +import boostcamp.and07.mindsync.ui.mindmap.MindMapViewModel class EditDescriptionDialog : DialogFragment() { private var _binding: DialogEditDescriptionBinding? = null private val binding get() = _binding!! private lateinit var editDialogInterface: EditDialogInterface - - fun setListener(listener: EditDialogInterface) { - editDialogInterface = listener - } + private val mindMapViewModel: MindMapViewModel by navGraphViewModels(R.id.nav_graph) + private val args: EditDescriptionDialogArgs by navArgs() override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val dialog = super.onCreateDialog(savedInstanceState) @@ -36,16 +41,46 @@ class EditDescriptionDialog : DialogFragment() { savedInstanceState: Bundle?, ): View { _binding = DialogEditDescriptionBinding.inflate(inflater, container, false) - binding.run { - btnCancel.setOnClickListener { - dismiss() - } - btnSubmit.setOnClickListener { - editDialogInterface.onSubmitClick(binding.etNodeDescription.text.toString()) - dismiss() + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + setupCancelBtn() + setupSubmitBtn() + } + + private fun setupSubmitBtn() { + binding.btnSubmit.setOnClickListener { + val node = args.node + val description = binding.etNodeDescription.text.toString() + when (args.operation) { + OperationType.ADD -> { + mindMapViewModel.addNode(node, NodeGenerator.makeNode(description, node.id)) + } + + OperationType.UPDATE -> { + val newNode = + when (node) { + is CircleNode -> node.copy(description = description) + is RectangleNode -> node.copy(description = description) + } + mindMapViewModel.updateNode(newNode) + } + + else -> {} } + dismiss() + } + } + + private fun setupCancelBtn() { + binding.btnCancel.setOnClickListener { + dismiss() } - return binding.root } override fun onStart() { diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/main/MainActivity.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/main/MainActivity.kt index 90d22b30..e3855a5e 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/main/MainActivity.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/main/MainActivity.kt @@ -41,6 +41,7 @@ class MainActivity : super.onStart() mainViewModel.fetchProfile() mainViewModel.getSpaces() + setTitle() } override fun init() { @@ -52,7 +53,6 @@ class MainActivity : setSideBarNavigation() setBinding() observeEvent() - setTitle() } override fun getViewModel(): BaseActivityViewModel { diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/main/MainViewModel.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/main/MainViewModel.kt index 6421c794..93611de8 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/main/MainViewModel.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/main/MainViewModel.kt @@ -49,10 +49,18 @@ class MainViewModel fun getSpaces() { viewModelScope.launch(coroutineExceptionHandler) { - profileSpaceRepository.getSpaces().collectLatest { spaces -> + profileSpaceRepository.getSpaces().collectLatest { responseSpaces -> + val newSpaces = + responseSpaces.map { space -> + if (space.id == _uiState.value.nowSpace?.id) { + space.copy(isSelected = true) + } else { + space + } + } _uiState.update { uiState -> uiState.copy( - spaces = spaces, + spaces = newSpaces, ) } } diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/mindmap/MindMapFragment.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/mindmap/MindMapFragment.kt index b77a0470..e50df17f 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/mindmap/MindMapFragment.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/mindmap/MindMapFragment.kt @@ -1,22 +1,19 @@ package boostcamp.and07.mindsync.ui.mindmap import android.util.Log -import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs +import androidx.navigation.navGraphViewModels import boostcamp.and07.mindsync.R -import boostcamp.and07.mindsync.data.NodeGenerator -import boostcamp.and07.mindsync.data.model.CircleNode +import boostcamp.and07.mindsync.data.crdt.OperationType import boostcamp.and07.mindsync.data.model.Node -import boostcamp.and07.mindsync.data.model.RectangleNode import boostcamp.and07.mindsync.data.model.Tree import boostcamp.and07.mindsync.data.network.SocketState import boostcamp.and07.mindsync.databinding.FragmentMindMapBinding import boostcamp.and07.mindsync.ui.base.BaseFragment -import boostcamp.and07.mindsync.ui.dialog.EditDescriptionDialog -import boostcamp.and07.mindsync.ui.dialog.EditDialogInterface import boostcamp.and07.mindsync.ui.util.Dp import boostcamp.and07.mindsync.ui.util.Px import boostcamp.and07.mindsync.ui.util.ThrottleDuration @@ -36,7 +33,9 @@ class MindMapFragment : NodeClickListener, TreeUpdateListener, NodeMoveListener { - private val mindMapViewModel: MindMapViewModel by viewModels() + private val mindMapViewModel: MindMapViewModel by navGraphViewModels(R.id.nav_graph) { + MindMapViewModelFactory() + } private lateinit var mindMapContainer: MindMapContainer private val args: MindMapFragmentArgs by navArgs() @@ -111,42 +110,39 @@ class MindMapFragment : } private fun showDialog( - selectNode: Node, - action: (Node, String) -> Unit, + operationType: OperationType, + selectedNode: Node, ) { - val dialog = EditDescriptionDialog() - dialog.setListener( - object : EditDialogInterface { - override fun onSubmitClick(description: String) { - action.invoke(selectNode, description) - } - }, + findNavController().navigate( + MindMapFragmentDirections.actionMindMapFragmentToEditDescriptionDialog( + operationType, + selectedNode, + ), ) - dialog.show(requireActivity().supportFragmentManager, "EditDescriptionDialog") } private fun setClickEventThrottle() { with(binding) { - imgbtnMindMapAdd.setClickEvent(lifecycleScope, ThrottleDuration.SHORT_DURATION.duration) { + imgbtnMindMapAdd.setClickEvent( + lifecycleScope, + ThrottleDuration.SHORT_DURATION.duration, + ) { mindMapViewModel.selectedNode.value?.let { selectNode -> - showDialog(selectNode) { parent, description -> - mindMapViewModel.addNode(parent, NodeGenerator.makeNode(description, parent.id)) - } + showDialog(OperationType.ADD, selectNode) } } - imgbtnMindMapEdit.setClickEvent(lifecycleScope, ThrottleDuration.SHORT_DURATION.duration) { + imgbtnMindMapEdit.setClickEvent( + lifecycleScope, + ThrottleDuration.SHORT_DURATION.duration, + ) { mindMapViewModel.selectedNode.value?.let { selectNode -> - showDialog(selectNode) { node, description -> - val newNode = - when (node) { - is CircleNode -> node.copy(description = description) - is RectangleNode -> node.copy(description = description) - } - mindMapViewModel.updateNode(newNode) - } + showDialog(OperationType.UPDATE, selectNode) } } - imgbtnMindMapRemove.setClickEvent(lifecycleScope, ThrottleDuration.SHORT_DURATION.duration) { + imgbtnMindMapRemove.setClickEvent( + lifecycleScope, + ThrottleDuration.SHORT_DURATION.duration, + ) { mindMapViewModel.selectedNode.value?.let { selectNode -> mindMapViewModel.removeNode(selectNode) } diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/mindmap/MindMapViewModel.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/mindmap/MindMapViewModel.kt index cf1cc522..7ec4c61b 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/mindmap/MindMapViewModel.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/mindmap/MindMapViewModel.kt @@ -19,7 +19,6 @@ import boostcamp.and07.mindsync.data.network.SocketEventType import boostcamp.and07.mindsync.data.network.SocketState import boostcamp.and07.mindsync.data.network.response.mindmap.SerializedCrdtTree import boostcamp.and07.mindsync.data.network.response.mindmap.SerializedOperation -import boostcamp.and07.mindsync.data.repository.mindmap.MindMapRepository import boostcamp.and07.mindsync.ui.util.Dp import boostcamp.and07.mindsync.ui.util.ExceptionMessage import dagger.hilt.android.lifecycle.HiltViewModel @@ -32,9 +31,7 @@ import javax.inject.Inject @HiltViewModel class MindMapViewModel @Inject - constructor( - private val mindMapRepository: MindMapRepository, - ) : ViewModel() { + constructor() : ViewModel() { private var boardId: String = "" val crdtTree = CrdtTree(id = IdGenerator.makeRandomNodeId(), tree = Tree()) private var _selectedNode = MutableStateFlow(null) diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/mindmap/MindMapViewModelFactory.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/mindmap/MindMapViewModelFactory.kt new file mode 100644 index 00000000..ceed6148 --- /dev/null +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/mindmap/MindMapViewModelFactory.kt @@ -0,0 +1,15 @@ +package boostcamp.and07.mindsync.ui.mindmap + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider + +class MindMapViewModelFactory : + ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(MindMapViewModel::class.java)) { + @Suppress("UNCHECKED_CAST") + return MindMapViewModel() as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/recyclebin/RecycleBinAdapter.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/recyclebin/RecycleBinAdapter.kt new file mode 100644 index 00000000..df201a30 --- /dev/null +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/recyclebin/RecycleBinAdapter.kt @@ -0,0 +1,70 @@ +package boostcamp.and07.mindsync.ui.recyclebin + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import boostcamp.and07.mindsync.data.model.Board +import boostcamp.and07.mindsync.databinding.ItemRecycleBoardBinding + +class RecycleBinAdapter : + ListAdapter(DIFF_CALLBACK) { + private var clickListener: RecycleBinClickListener? = null + + fun setRecycleBinClickListener(listener: RecycleBinClickListener) { + this.clickListener = listener + } + + class RecycleBinViewHolder( + private val binding: ItemRecycleBoardBinding, + private val clickListener: RecycleBinClickListener?, + ) : + RecyclerView.ViewHolder(binding.root) { + fun bind(item: Board) { + with(binding) { + board = item + cbBoard.isChecked = item.isChecked + cbBoard.setOnClickListener { + item.isChecked = !item.isChecked + clickListener?.onCheckBoxClick(item) + } + } + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): RecycleBinViewHolder { + val binding = + ItemRecycleBoardBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return RecycleBinViewHolder(binding, clickListener) + } + + override fun onBindViewHolder( + holder: RecycleBinViewHolder, + position: Int, + ) { + holder.bind(getItem(position)) + } + + companion object { + val DIFF_CALLBACK = + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: Board, + newItem: Board, + ): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame( + oldItem: Board, + newItem: Board, + ): Boolean { + return oldItem == newItem + } + } + } +} diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/recyclebin/RecycleBinBindingAdpater.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/recyclebin/RecycleBinBindingAdpater.kt new file mode 100644 index 00000000..075dbd43 --- /dev/null +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/recyclebin/RecycleBinBindingAdpater.kt @@ -0,0 +1,12 @@ +package boostcamp.and07.mindsync.ui.recyclebin + +import androidx.databinding.BindingAdapter +import androidx.recyclerview.widget.RecyclerView +import boostcamp.and07.mindsync.data.model.Board + +@BindingAdapter("app:restoreBoards") +fun RecyclerView.bindRestoreBoards(boards: List) { + if (this.adapter != null) { + (this.adapter as RecycleBinAdapter).submitList(boards.toMutableList()) + } +} diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/recyclebin/RecycleBinClickListener.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/recyclebin/RecycleBinClickListener.kt new file mode 100644 index 00000000..1ff46e9e --- /dev/null +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/recyclebin/RecycleBinClickListener.kt @@ -0,0 +1,7 @@ +package boostcamp.and07.mindsync.ui.recyclebin + +import boostcamp.and07.mindsync.data.model.Board + +interface RecycleBinClickListener { + fun onCheckBoxClick(board: Board) +} diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/recyclebin/RecycleBinFragment.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/recyclebin/RecycleBinFragment.kt index 7b8bc3bc..db350f2f 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/recyclebin/RecycleBinFragment.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/recyclebin/RecycleBinFragment.kt @@ -1,21 +1,17 @@ package boostcamp.and07.mindsync.ui.recyclebin import androidx.fragment.app.viewModels -import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import boostcamp.and07.mindsync.R import boostcamp.and07.mindsync.data.model.Board import boostcamp.and07.mindsync.databinding.FragmentRecycleBinBinding import boostcamp.and07.mindsync.ui.base.BaseFragment -import boostcamp.and07.mindsync.ui.boardlist.BoardClickListener -import boostcamp.and07.mindsync.ui.boardlist.BoardListAdapter -import boostcamp.and07.mindsync.ui.boardlist.BoardListFragmentDirections import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class RecycleBinFragment : BaseFragment(R.layout.fragment_recycle_bin) { private val recycleBinViewModel: RecycleBinViewModel by viewModels() - private val boardListAdapter = BoardListAdapter() + private val recycleBinAdapter = RecycleBinAdapter() private val args: RecycleBinFragmentArgs by navArgs() override fun initView() { @@ -26,18 +22,9 @@ class RecycleBinFragment : BaseFragment(R.layout.frag private fun setBinding() { binding.vm = recycleBinViewModel - binding.rvRecyclebinBoard.adapter = boardListAdapter - boardListAdapter.setBoardClickListener( - object : BoardClickListener { - override fun onClick(board: Board) { - findNavController().navigate( - BoardListFragmentDirections.actionBoardListFragmentToMindMapFragment( - boardId = board.id, - boardName = board.name, - ), - ) - } - + binding.rvRecyclebinBoard.adapter = recycleBinAdapter + recycleBinAdapter.setRecycleBinClickListener( + object : RecycleBinClickListener { override fun onCheckBoxClick(board: Board) { recycleBinViewModel.selectBoard(board) } diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/util/DensityUtils.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/util/DensityUtils.kt index 217b8641..adda668c 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/util/DensityUtils.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/util/DensityUtils.kt @@ -2,7 +2,9 @@ package boostcamp.and07.mindsync.ui.util import android.content.Context import android.util.TypedValue +import kotlinx.serialization.Serializable +@Serializable data class Dp(val dpVal: Float) { operator fun plus(dpValue: Dp): Dp { return Dp(dpVal + dpValue.dpVal) diff --git a/AOS/app/src/main/res/layout/fragment_recycle_bin.xml b/AOS/app/src/main/res/layout/fragment_recycle_bin.xml index 57e2d285..83a66204 100644 --- a/AOS/app/src/main/res/layout/fragment_recycle_bin.xml +++ b/AOS/app/src/main/res/layout/fragment_recycle_bin.xml @@ -43,7 +43,7 @@ android:layout_gravity="center|top" android:layout_marginTop="15dp" android:background="@android:color/transparent" - app:boards="@{vm.uiState.boards}" + app:restoreBoards="@{vm.uiState.boards}" app:flexBoxLayoutManager="@{FlexDirection.ROW}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/gl_recyclebin_end" diff --git a/AOS/app/src/main/res/navigation/nav_graph.xml b/AOS/app/src/main/res/navigation/nav_graph.xml index 37a95504..5959ee83 100644 --- a/AOS/app/src/main/res/navigation/nav_graph.xml +++ b/AOS/app/src/main/res/navigation/nav_graph.xml @@ -18,6 +18,9 @@ + + android:label="EditDescriptionDialog" > + + +