test: cover board detail create and filter user flows
This commit is contained in:
@@ -9,10 +9,12 @@ import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.Espresso.pressBack
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.longClick
|
||||
import androidx.test.espresso.action.ViewActions.replaceText
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra
|
||||
@@ -28,6 +30,7 @@ import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.chip.Chip
|
||||
import java.text.DateFormat
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
import java.util.ArrayDeque
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
@@ -65,6 +68,7 @@ class BoardDetailFlowTest {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
observedStates.clear()
|
||||
MainActivity.dependencies.clear()
|
||||
MainActivity.dependencies.sessionStoreFactory = { InMemorySessionStore("https://kan.bn/") }
|
||||
MainActivity.dependencies.apiKeyStoreFactory = { InMemoryApiKeyStore("api") }
|
||||
@@ -357,6 +361,203 @@ class BoardDetailFlowTest {
|
||||
onView(withText("Add new card")).inRoot(isDialog()).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fabChooser_addList_createAndValidationFlow() {
|
||||
launchBoardDetail()
|
||||
|
||||
onView(withId(R.id.boardDetailCreateFab)).perform(click())
|
||||
onView(withText(R.string.add_new_list)).inRoot(isDialog()).perform(click())
|
||||
onView(withText(R.string.add_new_list)).inRoot(isDialog()).check(matches(isDisplayed()))
|
||||
|
||||
onView(withText(R.string.add_list)).inRoot(isDialog()).perform(click())
|
||||
onView(withText(R.string.list_title_required)).inRoot(isDialog()).check(matches(isDisplayed()))
|
||||
|
||||
onView(withId(R.id.addListTitleInput)).inRoot(isDialog()).perform(replaceText("Created List"))
|
||||
onView(withText(R.string.add_list)).inRoot(isDialog()).perform(click())
|
||||
|
||||
awaitCondition {
|
||||
defaultDataSource.createListCalls == 1 && observedStates.lastOrNull()?.isMutating == false
|
||||
}
|
||||
|
||||
assertEquals(1, defaultDataSource.createListCalls)
|
||||
assertEquals("board-1", defaultDataSource.lastCreateListBoardId)
|
||||
assertEquals("Created List", defaultDataSource.lastCreateListTitle)
|
||||
assertTrue(
|
||||
observedStates.lastOrNull()
|
||||
?.boardDetail
|
||||
?.lists
|
||||
?.any { it.title == "Created List" } == true,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fabChooser_addCard_createWithTagsAndClearDate() {
|
||||
defaultDataSource.currentDetail = detailCreateCardDialogTagCatalog()
|
||||
val scenario = launchBoardDetail()
|
||||
|
||||
onView(withId(R.id.boardDetailCreateFab)).perform(click())
|
||||
onView(withText(R.string.add_new_card)).inRoot(isDialog()).perform(click())
|
||||
|
||||
onView(withId(R.id.addCardTitleInput)).inRoot(isDialog()).perform(replaceText("Created Card"))
|
||||
onView(withId(R.id.addCardDescriptionInput)).inRoot(isDialog()).perform(replaceText("Body"))
|
||||
onView(withText("Backend")).inRoot(isDialog()).perform(click())
|
||||
onView(withId(R.id.addCardDueDateText)).inRoot(isDialog()).perform(click())
|
||||
onView(withText(android.R.string.ok)).inRoot(isDialog()).perform(click())
|
||||
awaitCondition {
|
||||
observedStates.lastOrNull()?.addCardDueDate != null
|
||||
}
|
||||
scenario.onActivity { }
|
||||
onView(withId(R.id.addCardClearDueDateAction)).inRoot(isDialog()).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.addCardClearDueDateAction)).inRoot(isDialog()).perform(click())
|
||||
awaitCondition {
|
||||
observedStates.lastOrNull()?.addCardDueDate == null
|
||||
}
|
||||
onView(withText(R.string.add_card)).inRoot(isDialog()).perform(click())
|
||||
|
||||
awaitCondition {
|
||||
defaultDataSource.createCardCalls == 1 && observedStates.lastOrNull()?.isMutating == false
|
||||
}
|
||||
|
||||
assertEquals(1, defaultDataSource.createCardCalls)
|
||||
assertEquals("list-1", defaultDataSource.lastCreateCardListId)
|
||||
assertEquals("Created Card", defaultDataSource.lastCreateCardTitle)
|
||||
assertEquals("Body", defaultDataSource.lastCreateCardDescription)
|
||||
assertEquals(setOf("tag-1"), defaultDataSource.lastCreateCardTagIds)
|
||||
assertNull(defaultDataSource.lastCreateCardDueDate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fabChooser_zeroLists_disablesAddCard_andShowsHelperMessage() {
|
||||
defaultDataSource.currentDetail = BoardDetail(id = "board-1", title = "Board", lists = emptyList())
|
||||
launchBoardDetail()
|
||||
|
||||
onView(withId(R.id.boardDetailCreateFab)).perform(click())
|
||||
onView(withText(R.string.add_new_card)).inRoot(isDialog()).check(matches(not(isEnabled())))
|
||||
onView(withText(R.string.create_a_list_first_to_add_cards)).inRoot(isDialog()).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filters_applyTagAnyAndSearchAnd_keepAllListsVisible() {
|
||||
defaultDataSource.currentDetail = detailFilterAndSearchThreeLists()
|
||||
val scenario = launchBoardDetail()
|
||||
|
||||
onView(withContentDescription("Filter by tag")).perform(click())
|
||||
onView(withText("Backend")).inRoot(isDialog()).perform(click())
|
||||
onView(withText("Mobile")).inRoot(isDialog()).perform(click())
|
||||
onView(withText(R.string.filter)).inRoot(isDialog()).perform(click())
|
||||
|
||||
onView(withContentDescription("Search")).perform(click())
|
||||
onView(withId(R.id.searchTitleInput)).inRoot(isDialog()).perform(replaceText("Duke"))
|
||||
onView(withText(R.string.search)).inRoot(isDialog()).perform(click())
|
||||
|
||||
onView(withText("Duke Backend")).check(matches(isDisplayed()))
|
||||
scenario.onActivity { activity ->
|
||||
activity.findViewById<ViewPager2>(R.id.boardDetailPager).setCurrentItem(1, false)
|
||||
}
|
||||
onView(withText("Duke Mobile")).check(matches(isDisplayed()))
|
||||
scenario.onActivity { activity ->
|
||||
activity.findViewById<ViewPager2>(R.id.boardDetailPager).setCurrentItem(2, false)
|
||||
}
|
||||
onView(withText("Done")).check(matches(isDisplayed()))
|
||||
onView(withText(R.string.board_detail_empty_list)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filterAndSearchActions_showActiveTintWhenCriteriaApplied_andResetOnClear() {
|
||||
defaultDataSource.currentDetail = detailFilterAndSearchThreeLists()
|
||||
val scenario = launchBoardDetail()
|
||||
|
||||
onView(withContentDescription("Filter by tag")).perform(click())
|
||||
onView(withText("Backend")).inRoot(isDialog()).perform(click())
|
||||
onView(withText(R.string.filter)).inRoot(isDialog()).perform(click())
|
||||
|
||||
onView(withContentDescription("Search")).perform(click())
|
||||
onView(withId(R.id.searchTitleInput)).inRoot(isDialog()).perform(replaceText("Duke"))
|
||||
onView(withText(R.string.search)).inRoot(isDialog()).perform(click())
|
||||
|
||||
scenario.onActivity { activity ->
|
||||
val menu = activity.findViewById<com.google.android.material.appbar.MaterialToolbar>(R.id.boardDetailToolbar).menu
|
||||
val expectedActive = MaterialColors.getColor(
|
||||
activity,
|
||||
com.google.android.material.R.attr.colorPrimary,
|
||||
Color.BLUE,
|
||||
)
|
||||
assertEquals(expectedActive, menu.findItem(R.id.actionFilterByTag).iconTintList?.defaultColor)
|
||||
assertEquals(expectedActive, menu.findItem(R.id.actionSearch).iconTintList?.defaultColor)
|
||||
}
|
||||
|
||||
onView(withContentDescription("Filter by tag")).perform(click())
|
||||
onView(withContentDescription("Search")).perform(click())
|
||||
|
||||
scenario.onActivity { activity ->
|
||||
val menu = activity.findViewById<com.google.android.material.appbar.MaterialToolbar>(R.id.boardDetailToolbar).menu
|
||||
val expectedInactive = MaterialColors.getColor(
|
||||
activity,
|
||||
com.google.android.material.R.attr.colorOnSurfaceVariant,
|
||||
Color.BLACK,
|
||||
)
|
||||
assertEquals(expectedInactive, menu.findItem(R.id.actionFilterByTag).iconTintList?.defaultColor)
|
||||
assertEquals(expectedInactive, menu.findItem(R.id.actionSearch).iconTintList?.defaultColor)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activeFilterOrSearchIconTap_clearsCriterionImmediately() {
|
||||
defaultDataSource.currentDetail = detailFilterAndSearchThreeLists()
|
||||
val scenario = launchBoardDetail()
|
||||
|
||||
onView(withContentDescription("Filter by tag")).perform(click())
|
||||
onView(withText("Backend")).inRoot(isDialog()).perform(click())
|
||||
onView(withText(R.string.filter)).inRoot(isDialog()).perform(click())
|
||||
onView(withText("Duke Mobile")).check(doesNotExist())
|
||||
|
||||
onView(withContentDescription("Filter by tag")).perform(click())
|
||||
scenario.onActivity { activity ->
|
||||
activity.findViewById<ViewPager2>(R.id.boardDetailPager).setCurrentItem(1, false)
|
||||
}
|
||||
onView(withText("Duke Mobile")).check(matches(isDisplayed()))
|
||||
|
||||
onView(withContentDescription("Search")).perform(click())
|
||||
onView(withId(R.id.searchTitleInput)).inRoot(isDialog()).perform(replaceText("Backend"))
|
||||
onView(withText(R.string.search)).inRoot(isDialog()).perform(click())
|
||||
onView(withText(R.string.board_detail_empty_list)).check(matches(isDisplayed()))
|
||||
|
||||
onView(withContentDescription("Search")).perform(click())
|
||||
scenario.onActivity { activity ->
|
||||
activity.findViewById<ViewPager2>(R.id.boardDetailPager).setCurrentItem(1, false)
|
||||
}
|
||||
onView(withText("Duke Mobile")).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun selectionMode_hidesFilterAndSearchActions() {
|
||||
val scenario = launchBoardDetail()
|
||||
|
||||
onView(withText("Card 1")).perform(longClick())
|
||||
|
||||
scenario.onActivity { activity ->
|
||||
val menu = activity.findViewById<com.google.android.material.appbar.MaterialToolbar>(R.id.boardDetailToolbar).menu
|
||||
assertNull(menu.findItem(R.id.actionFilterByTag))
|
||||
assertNull(menu.findItem(R.id.actionSearch))
|
||||
assertNotNull(menu.findItem(R.id.actionSelectAll))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun backArrowAndSystemBack_clearSelectionBeforeNavigation() {
|
||||
launchBoardDetail()
|
||||
onView(withText("Card 1")).perform(longClick())
|
||||
onView(withId(R.id.actionSelectAll)).check(matches(isDisplayed()))
|
||||
onView(withContentDescription(androidx.appcompat.R.string.abc_action_bar_up_description)).perform(click())
|
||||
onView(withId(R.id.actionFilterByTag)).check(matches(isDisplayed()))
|
||||
|
||||
launchBoardDetail()
|
||||
onView(withText("Card 1")).perform(longClick())
|
||||
onView(withId(R.id.actionSelectAll)).check(matches(isDisplayed()))
|
||||
pressBack()
|
||||
onView(withId(R.id.actionFilterByTag)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingBoardIdShowsBlockingDialogAndFinishes() {
|
||||
val scenario = launchBoardDetail(boardId = null)
|
||||
@@ -682,12 +883,37 @@ class BoardDetailFlowTest {
|
||||
var deleteGate: CompletableDeferred<Unit>? = null
|
||||
var moveCalls: Int = 0
|
||||
var deleteCalls: Int = 0
|
||||
var createListCalls: Int = 0
|
||||
var createCardCalls: Int = 0
|
||||
var lastMoveCardIds: Set<String> = emptySet()
|
||||
var lastDeleteCardIds: Set<String> = emptySet()
|
||||
var lastMoveTargetListId: String? = null
|
||||
var lastCreateListBoardId: String? = null
|
||||
var lastCreateListTitle: String? = null
|
||||
var lastCreateCardListId: String? = null
|
||||
var lastCreateCardTitle: String? = null
|
||||
var lastCreateCardDescription: String? = null
|
||||
var lastCreateCardDueDate: LocalDate? = null
|
||||
var lastCreateCardTagIds: Set<String> = emptySet()
|
||||
var createListResult: BoardsApiResult<CreatedEntityRef> = BoardsApiResult.Success(CreatedEntityRef("created-list"))
|
||||
var createCardResult: BoardsApiResult<CreatedEntityRef> = BoardsApiResult.Success(CreatedEntityRef("created-card"))
|
||||
|
||||
override suspend fun createList(boardPublicId: String, title: String): BoardsApiResult<CreatedEntityRef> {
|
||||
return BoardsApiResult.Failure("Not implemented in test fake")
|
||||
createListCalls += 1
|
||||
lastCreateListBoardId = boardPublicId
|
||||
lastCreateListTitle = title
|
||||
val result = createListResult
|
||||
if (result is BoardsApiResult.Success) {
|
||||
val newId = result.value.publicId?.trim().orEmpty().ifBlank { "created-list-${createListCalls}" }
|
||||
currentDetail = currentDetail.copy(
|
||||
lists = currentDetail.lists + BoardListDetail(
|
||||
id = newId,
|
||||
title = title,
|
||||
cards = emptyList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override suspend fun createCard(
|
||||
@@ -697,7 +923,47 @@ class BoardDetailFlowTest {
|
||||
dueDate: LocalDate?,
|
||||
tagPublicIds: Collection<String>,
|
||||
): BoardsApiResult<CreatedEntityRef> {
|
||||
return BoardsApiResult.Failure("Not implemented in test fake")
|
||||
createCardCalls += 1
|
||||
lastCreateCardListId = listPublicId
|
||||
lastCreateCardTitle = title
|
||||
lastCreateCardDescription = description
|
||||
lastCreateCardDueDate = dueDate
|
||||
lastCreateCardTagIds = tagPublicIds.toSet()
|
||||
|
||||
val result = createCardResult
|
||||
if (result is BoardsApiResult.Success) {
|
||||
val newId = result.value.publicId?.trim().orEmpty().ifBlank { "created-card-${createCardCalls}" }
|
||||
val knownTagsById = currentDetail.lists
|
||||
.asSequence()
|
||||
.flatMap { list -> list.cards.asSequence() }
|
||||
.flatMap { card -> card.tags.asSequence() }
|
||||
.associateBy { tag -> tag.id }
|
||||
val newTags = tagPublicIds.mapNotNull { knownTagsById[it] }
|
||||
val dueAt = dueDate
|
||||
?.atStartOfDay(ZoneId.systemDefault())
|
||||
?.toInstant()
|
||||
?.toEpochMilli()
|
||||
|
||||
currentDetail = currentDetail.copy(
|
||||
lists = currentDetail.lists.map { list ->
|
||||
if (list.id == listPublicId) {
|
||||
list.copy(
|
||||
cards = listOf(
|
||||
BoardCardSummary(
|
||||
id = newId,
|
||||
title = title,
|
||||
tags = newTags,
|
||||
dueAtEpochMillis = dueAt,
|
||||
),
|
||||
) + list.cards,
|
||||
)
|
||||
} else {
|
||||
list
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override suspend fun getBoardDetail(boardId: String): BoardsApiResult<BoardDetail> {
|
||||
@@ -1026,5 +1292,74 @@ class BoardDetailFlowTest {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun detailCreateCardDialogTagCatalog(): BoardDetail {
|
||||
return BoardDetail(
|
||||
id = "board-1",
|
||||
title = "Board",
|
||||
lists = listOf(
|
||||
BoardListDetail(
|
||||
id = "list-1",
|
||||
title = "To Do",
|
||||
cards = listOf(
|
||||
BoardCardSummary(
|
||||
id = "card-1",
|
||||
title = "Existing",
|
||||
tags = listOf(
|
||||
BoardTagSummary("tag-1", "Backend", "#008080"),
|
||||
BoardTagSummary("tag-2", "Mobile", "#990000"),
|
||||
),
|
||||
dueAtEpochMillis = null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun detailFilterAndSearchThreeLists(): BoardDetail {
|
||||
return BoardDetail(
|
||||
id = "board-1",
|
||||
title = "Board",
|
||||
lists = listOf(
|
||||
BoardListDetail(
|
||||
id = "list-1",
|
||||
title = "To Do",
|
||||
cards = listOf(
|
||||
BoardCardSummary(
|
||||
id = "todo-duke-backend",
|
||||
title = "Duke Backend",
|
||||
tags = listOf(BoardTagSummary("tag-1", "Backend", "#008080")),
|
||||
dueAtEpochMillis = null,
|
||||
),
|
||||
),
|
||||
),
|
||||
BoardListDetail(
|
||||
id = "list-2",
|
||||
title = "Doing",
|
||||
cards = listOf(
|
||||
BoardCardSummary(
|
||||
id = "doing-duke-mobile",
|
||||
title = "Duke Mobile",
|
||||
tags = listOf(BoardTagSummary("tag-2", "Mobile", "#009900")),
|
||||
dueAtEpochMillis = null,
|
||||
),
|
||||
),
|
||||
),
|
||||
BoardListDetail(
|
||||
id = "list-3",
|
||||
title = "Done",
|
||||
cards = listOf(
|
||||
BoardCardSummary(
|
||||
id = "done-archived",
|
||||
title = "Archived",
|
||||
tags = listOf(BoardTagSummary("tag-3", "Ops", "#333333")),
|
||||
dueAtEpochMillis = null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package space.hackenslacker.kanbn4droid.app.boarddetail
|
||||
|
||||
import android.app.DatePickerDialog
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
@@ -29,6 +30,9 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import java.text.DateFormat
|
||||
import java.time.ZoneId
|
||||
import java.util.Date
|
||||
import kotlinx.coroutines.launch
|
||||
import space.hackenslacker.kanbn4droid.app.MainActivity
|
||||
import space.hackenslacker.kanbn4droid.app.CardDetailPlaceholderActivity
|
||||
@@ -175,7 +179,7 @@ class BoardDetailActivity : AppCompatActivity() {
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.title = intent.getStringExtra(EXTRA_BOARD_TITLE).orEmpty()
|
||||
toolbar.setNavigationOnClickListener {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
onBackPressed()
|
||||
}
|
||||
retryButton.setOnClickListener {
|
||||
viewModel.retryLoad()
|
||||
@@ -286,7 +290,7 @@ class BoardDetailActivity : AppCompatActivity() {
|
||||
}
|
||||
initialProgress.visibility = if (state.isInitialLoading && state.boardDetail == null) View.VISIBLE else View.GONE
|
||||
|
||||
val boardLists = state.boardDetail?.lists.orEmpty()
|
||||
val boardLists = state.filteredBoardDetail?.lists.orEmpty()
|
||||
val applyPagerState = {
|
||||
pagerAdapter.submit(
|
||||
lists = boardLists,
|
||||
@@ -366,9 +370,15 @@ class BoardDetailActivity : AppCompatActivity() {
|
||||
val titleLayout = dialog.findViewById<TextInputLayout>(R.id.addCardTitleLayout)
|
||||
val titleInput = dialog.findViewById<TextInputEditText>(R.id.addCardTitleInput)
|
||||
val descriptionInput = dialog.findViewById<TextInputEditText>(R.id.addCardDescriptionInput)
|
||||
val dueDateText = dialog.findViewById<TextView>(R.id.addCardDueDateText)
|
||||
val clearDueDateAction = dialog.findViewById<TextView>(R.id.addCardClearDueDateAction)
|
||||
titleLayout?.error = state.addCardTitleError
|
||||
titleInput?.isEnabled = !state.isMutating
|
||||
descriptionInput?.isEnabled = !state.isMutating
|
||||
dueDateText?.text = formatDueDateForDialog(state.addCardDueDate)
|
||||
dueDateText?.isEnabled = !state.isMutating
|
||||
clearDueDateAction?.visibility = if (state.addCardDueDate != null) View.VISIBLE else View.GONE
|
||||
clearDueDateAction?.isEnabled = !state.isMutating
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled = !state.isMutating
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)?.isEnabled = !state.isMutating
|
||||
if (!state.isAddCardDialogOpen) {
|
||||
@@ -516,6 +526,7 @@ class BoardDetailActivity : AppCompatActivity() {
|
||||
}
|
||||
DrawableCompat.setTint(wrapped, tintColor)
|
||||
item?.icon = wrapped
|
||||
item?.iconTintList = android.content.res.ColorStateList.valueOf(tintColor)
|
||||
}
|
||||
|
||||
private fun showFabChooserDialog(state: BoardDetailUiState) {
|
||||
@@ -591,10 +602,37 @@ class BoardDetailActivity : AppCompatActivity() {
|
||||
val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_add_card, null)
|
||||
val titleInput: TextInputEditText = dialogView.findViewById(R.id.addCardTitleInput)
|
||||
val descriptionInput: TextInputEditText = dialogView.findViewById(R.id.addCardDescriptionInput)
|
||||
val dueDateText: TextView = dialogView.findViewById(R.id.addCardDueDateText)
|
||||
val clearDueDateAction: TextView = dialogView.findViewById(R.id.addCardClearDueDateAction)
|
||||
val tagsPlaceholderText: TextView = dialogView.findViewById(R.id.addCardTagsPlaceholderText)
|
||||
val tagsContainer: LinearLayout = dialogView.findViewById(R.id.addCardTagsContainer)
|
||||
val tags = state.boardDetail
|
||||
?.lists
|
||||
.orEmpty()
|
||||
.flatMap { it.cards }
|
||||
.flatMap { it.tags }
|
||||
.distinctBy { it.id }
|
||||
.sortedBy { it.name.lowercase() }
|
||||
|
||||
titleInput.setText(state.addCardTitleDraft)
|
||||
descriptionInput.setText(state.addCardDescriptionDraft)
|
||||
titleInput.doAfterTextChanged { viewModel.updateAddCardTitle(it?.toString().orEmpty()) }
|
||||
descriptionInput.doAfterTextChanged { viewModel.updateAddCardDescription(it?.toString().orEmpty()) }
|
||||
dueDateText.text = formatDueDateForDialog(state.addCardDueDate)
|
||||
clearDueDateAction.visibility = if (state.addCardDueDate != null) View.VISIBLE else View.GONE
|
||||
dueDateText.setOnClickListener { openAddCardDatePicker(state.addCardDueDate) }
|
||||
clearDueDateAction.setOnClickListener { viewModel.clearDueDate() }
|
||||
tagsPlaceholderText.visibility = if (tags.isEmpty()) View.VISIBLE else View.GONE
|
||||
tags.forEach { tag ->
|
||||
val checkBox = CheckBox(this).apply {
|
||||
text = tag.name
|
||||
isChecked = state.addCardSelectedTagIds.contains(tag.id)
|
||||
setOnCheckedChangeListener { _, _ ->
|
||||
viewModel.toggleAddCardTag(tag.id)
|
||||
}
|
||||
}
|
||||
tagsContainer.addView(checkBox)
|
||||
}
|
||||
|
||||
val dialog = MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.add_new_card)
|
||||
@@ -617,6 +655,30 @@ class BoardDetailActivity : AppCompatActivity() {
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun openAddCardDatePicker(currentDate: java.time.LocalDate?) {
|
||||
val seed = currentDate ?: java.time.LocalDate.now()
|
||||
DatePickerDialog(
|
||||
this,
|
||||
{ _, year, month, dayOfMonth ->
|
||||
viewModel.setDueDate(java.time.LocalDate.of(year, month + 1, dayOfMonth))
|
||||
},
|
||||
seed.year,
|
||||
seed.monthValue - 1,
|
||||
seed.dayOfMonth,
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun formatDueDateForDialog(dueDate: java.time.LocalDate?): String {
|
||||
if (dueDate == null) {
|
||||
return getString(R.string.due_date)
|
||||
}
|
||||
val epochMillis = dueDate
|
||||
.atStartOfDay(ZoneId.systemDefault())
|
||||
.toInstant()
|
||||
.toEpochMilli()
|
||||
return DateFormat.getDateInstance(DateFormat.MEDIUM).format(Date(epochMillis))
|
||||
}
|
||||
|
||||
private fun showFilterDialog(state: BoardDetailUiState) {
|
||||
val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_filter_tags, null)
|
||||
val container: LinearLayout = dialogView.findViewById(R.id.filterTagsContainer)
|
||||
|
||||
Reference in New Issue
Block a user