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

휴지통 화면, 보드 복구 API, 루트노드 일치화 #284

Merged
merged 19 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
@@ -1,9 +1,11 @@
package boostcamp.and07.mindsync.data.network

import boostcamp.and07.mindsync.data.network.request.board.DeleteBoardRequest
import boostcamp.and07.mindsync.data.network.request.board.RestoreBoardRequest
import boostcamp.and07.mindsync.data.network.response.board.BoardsResponse
import boostcamp.and07.mindsync.data.network.response.board.CreateBoardResponse
import boostcamp.and07.mindsync.data.network.response.board.DeleteBoardResponse
import boostcamp.and07.mindsync.data.network.response.board.RestoreBoardResponse
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
Expand Down Expand Up @@ -32,4 +34,9 @@ interface BoardApi {
suspend fun deleteBoard(
@Body deleteBoardRequest: DeleteBoardRequest,
): DeleteBoardResponse

@PATCH("boards/restore")
suspend fun restoreBoard(
@Body restoreRequest: RestoreBoardRequest,
): RestoreBoardResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package boostcamp.and07.mindsync.data.network.request.board

import kotlinx.serialization.Serializable

@Serializable
data class RestoreBoardRequest(
val boardId: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package boostcamp.and07.mindsync.data.network.response.board

import kotlinx.serialization.Serializable

@Serializable
data class RestoreBoardResponse(
val statusCode: Int,
val message: String,
val error: String? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ interface BoardListRepository {
imageUrl: MultipartBody.Part?,
): Flow<Board>

fun getBoard(spaceId: String): Flow<List<Board>>
fun getBoard(
spaceId: String,
isDeleted: Boolean,
): Flow<List<Board>>

fun deleteBoard(boardId: String): Flow<Boolean>

fun restoreBoard(boardId: String): Flow<Boolean>
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package boostcamp.and07.mindsync.data.repository.boardlist
import boostcamp.and07.mindsync.data.model.Board
import boostcamp.and07.mindsync.data.network.BoardApi
import boostcamp.and07.mindsync.data.network.request.board.DeleteBoardRequest
import boostcamp.and07.mindsync.data.network.request.board.RestoreBoardRequest
import boostcamp.and07.mindsync.ui.util.toRequestBody
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
Expand Down Expand Up @@ -38,11 +39,14 @@ class BoardListRepositoryImpl
}
}

override fun getBoard(spaceId: String): Flow<List<Board>> =
override fun getBoard(
spaceId: String,
isDeleted: Boolean,
): Flow<List<Board>> =
flow {
val response = boardApi.getBoards(spaceId)
emit(
response.data.filter { board -> board.isDeleted.not() }.map { board ->
response.data.filter { board -> board.isDeleted == isDeleted }.map { board ->
Board(
id = board.boardId,
name = board.boardName,
Expand All @@ -58,4 +62,10 @@ class BoardListRepositoryImpl
val response = boardApi.deleteBoard(DeleteBoardRequest(boardId))
emit(true)
}

override fun restoreBoard(boardId: String): Flow<Boolean> =
flow {
val response = boardApi.restoreBoard(RestoreBoardRequest(boardId))
emit(true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class BoardListAdapter : ListAdapter<Board, BoardListAdapter.BoardListViewHolder
oldItem: Board,
newItem: Board,
): Boolean {
return oldItem.id == newItem.id
return oldItem == newItem
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class BoardListViewModel

fun getBoards() {
viewModelScope.launch(coroutineExceptionHandler) {
boardListRepository.getBoard(_boardUiState.value.spaceId).collectLatest { boards ->
boardListRepository.getBoard(_boardUiState.value.spaceId, false).collectLatest { boards ->
_boardUiState.update { boardUiState ->
boardUiState.copy(boards = boards)
}
Expand Down Expand Up @@ -99,4 +99,23 @@ class BoardListViewModel
}
}
}

fun restoreBoard() {
viewModelScope.launch(coroutineExceptionHandler) {
val newBoards = boardUiState.value.boards.toMutableList()
val newSelectBoards = boardUiState.value.selectBoards.toMutableList()
_boardUiState.value.selectBoards.map { board ->
boardListRepository.restoreBoard(board.id).collectLatest {
newBoards.remove(board)
newSelectBoards.remove(board)
}
}
_boardUiState.update { boardUiState ->
boardUiState.copy(
boards = newBoards,
selectBoards = newSelectBoards,
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,20 @@ class MainActivity :
ThrottleDuration.LONG_DURATION.duration,
) {
drawerLayout.closeDrawers()
navController.navigate(R.id.action_to_recycleBinFragment)
mainViewModel.uiState.value.nowSpace?.let { nowSpace ->
drawerLayout.closeDrawers()
navController.navigate(
SpaceListFragmentDirections.actionToRecycleBinFragment(
spaceId = nowSpace.id,
),
)
} ?: run {
Toast.makeText(
this@MainActivity,
resources.getString(R.string.space_not_join),
Toast.LENGTH_SHORT,
).show()
}
}

imgbtnSideBarAddSpace.setClickEvent(
Expand Down Expand Up @@ -228,6 +241,7 @@ class MainActivity :
uiState.spaces.isEmpty() -> getString(R.string.app_start)
destination.id == R.id.spaceListFragment -> getString(R.string.space_list_title)
destination.id == R.id.boardListFragment -> getString(R.string.board_list_title)
destination.id == R.id.recycleBinFragment -> getString(R.string.recyclebin_title)
else -> ""
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class MindMapViewModel
private val mindMapRepository: MindMapRepository,
) : ViewModel() {
private var boardId: String = ""
val crdtTree = CrdtTree(IdGenerator.makeRandomNodeId())
val crdtTree = CrdtTree(id = IdGenerator.makeRandomNodeId(), tree = Tree())
Copy link
Collaborator

Choose a reason for hiding this comment

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

빼먹은 부분 수정해주셔서 감사합니다..ㅠㅠ
선배로 여길게요

private var _selectedNode = MutableStateFlow<Node?>(null)
val selectedNode: StateFlow<Node?> = _selectedNode
private val _operation = MutableStateFlow<Operation?>(null)
Expand All @@ -52,10 +52,14 @@ class MindMapViewModel
setSocketEvent()
}

fun setBoardId(boardId: String) {
fun setBoard(
boardId: String,
boardName: String,
) {
if (this.boardId != boardId) {
this.boardId = boardId
joinBoard(boardId, boardName)
updateNode(crdtTree.tree.getRootNode().copy(description = boardName))
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,53 @@
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<FragmentRecycleBinBinding>(R.layout.fragment_recycle_bin) {
private val recycleBinViewModel: RecycleBinViewModel by viewModels()
private val boardListAdapter = BoardListAdapter()
private val args: RecycleBinFragmentArgs by navArgs()

override fun initView() {
setBinding()
setBoardRestoreButton()
recycleBinViewModel.setSpace(args.spaceId)
}

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,
),
)
}

override fun onCheckBoxClick(board: Board) {
recycleBinViewModel.selectBoard(board)
}
},
)
}

private fun setBoardRestoreButton() {
binding.btnRecyclebinRestore.setOnClickListener {
recycleBinViewModel.restoreBoard()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package boostcamp.and07.mindsync.ui.recyclebin

sealed class RecycleBinUiEvent {
data object Success : RecycleBinUiEvent()

data class Error(val message: String) : RecycleBinUiEvent()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package boostcamp.and07.mindsync.ui.recyclebin

import boostcamp.and07.mindsync.data.model.Board

data class RecycleBinUiState(
val spaceId: String = "",
val boards: List<Board> = listOf(),
val selectBoards: List<Board> = listOf(),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package boostcamp.and07.mindsync.ui.recyclebin

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import boostcamp.and07.mindsync.data.model.Board
import boostcamp.and07.mindsync.data.repository.boardlist.BoardListRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class RecycleBinViewModel
@Inject
constructor(
private val boardListRepository: BoardListRepository,
) : ViewModel() {
private val _uiState = MutableStateFlow(RecycleBinUiState())
val uiState: StateFlow<RecycleBinUiState> = _uiState
private val _uiEvent = MutableSharedFlow<RecycleBinUiEvent>()
val uiEvent: SharedFlow<RecycleBinUiEvent> = _uiEvent

private val coroutineExceptionHandler =
CoroutineExceptionHandler { _, throwable ->
viewModelScope.launch {
_uiEvent.emit(RecycleBinUiEvent.Error(throwable.message.toString()))
}
}

fun setSpace(spaceId: String) {
_uiState.update { boardUiState ->
boardUiState.copy(
spaceId = spaceId,
)
}
getBoards()
}

fun getBoards() {
viewModelScope.launch(coroutineExceptionHandler) {
boardListRepository.getBoard(_uiState.value.spaceId, true).collectLatest { boards ->
_uiState.update { boardUiState ->
boardUiState.copy(boards = boards)
}
_uiEvent.emit(RecycleBinUiEvent.Success)
}
}
}

fun selectBoard(selectBoard: Board) {
_uiState.update { boardUiState ->
val newSelectBoards =
boardUiState.boards.toMutableList().filter { board -> board.isChecked }
boardUiState.copy(
selectBoards = newSelectBoards,
)
}
}

fun restoreBoard() {
viewModelScope.launch(coroutineExceptionHandler) {
val newBoards = uiState.value.boards.toMutableList()
val newSelectBoards = uiState.value.selectBoards.toMutableList()
_uiState.value.selectBoards.map { board ->
boardListRepository.restoreBoard(board.id).collectLatest {
newBoards.remove(board)
newSelectBoards.remove(board)
}
}
_uiState.update { boardUiState ->
boardUiState.copy(
boards = newBoards,
selectBoards = newSelectBoards,
)
}
}
}
}
5 changes: 5 additions & 0 deletions AOS/app/src/main/res/drawable/ic_refresh_board.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
</vector>
2 changes: 1 addition & 1 deletion AOS/app/src/main/res/layout/fragment_board_list.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@android:color/transparent"
android:src="@drawable/ic_restore_board"
android:src="@drawable/ic_refresh_board"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:rippleColor="@color/mindmap2" />
Expand Down
Loading