fix: finalize board detail action icons and startup guards
This commit is contained in:
@@ -5,6 +5,7 @@ import android.graphics.Color
|
|||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.test.core.app.ActivityScenario
|
import androidx.test.core.app.ActivityScenario
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import androidx.test.espresso.Espresso.onView
|
import androidx.test.espresso.Espresso.onView
|
||||||
@@ -17,7 +18,6 @@ import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
|||||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra
|
import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra
|
||||||
import androidx.test.espresso.matcher.RootMatchers.isDialog
|
import androidx.test.espresso.matcher.RootMatchers.isDialog
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
|
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
|
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
@@ -38,6 +38,7 @@ import org.junit.After
|
|||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@@ -48,6 +49,8 @@ import space.hackenslacker.kanbn4droid.app.boarddetail.BoardDetailDataSource
|
|||||||
import space.hackenslacker.kanbn4droid.app.boarddetail.BoardListDetail
|
import space.hackenslacker.kanbn4droid.app.boarddetail.BoardListDetail
|
||||||
import space.hackenslacker.kanbn4droid.app.boarddetail.BoardTagSummary
|
import space.hackenslacker.kanbn4droid.app.boarddetail.BoardTagSummary
|
||||||
import space.hackenslacker.kanbn4droid.app.boarddetail.CardBatchMutationResult
|
import space.hackenslacker.kanbn4droid.app.boarddetail.CardBatchMutationResult
|
||||||
|
import space.hackenslacker.kanbn4droid.app.auth.ApiKeyStore
|
||||||
|
import space.hackenslacker.kanbn4droid.app.auth.SessionStore
|
||||||
import space.hackenslacker.kanbn4droid.app.boards.BoardsApiResult
|
import space.hackenslacker.kanbn4droid.app.boards.BoardsApiResult
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@@ -59,6 +62,9 @@ class BoardDetailFlowTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
|
MainActivity.dependencies.clear()
|
||||||
|
MainActivity.dependencies.sessionStoreFactory = { InMemorySessionStore("https://kan.bn/") }
|
||||||
|
MainActivity.dependencies.apiKeyStoreFactory = { InMemoryApiKeyStore("api") }
|
||||||
originalLocale = Locale.getDefault()
|
originalLocale = Locale.getDefault()
|
||||||
Intents.init()
|
Intents.init()
|
||||||
defaultDataSource = FakeBoardDetailDataSource(initialDetail = detailOneList())
|
defaultDataSource = FakeBoardDetailDataSource(initialDetail = detailOneList())
|
||||||
@@ -71,6 +77,7 @@ class BoardDetailFlowTest {
|
|||||||
Intents.release()
|
Intents.release()
|
||||||
BoardDetailActivity.testDataSourceFactory = null
|
BoardDetailActivity.testDataSourceFactory = null
|
||||||
BoardDetailActivity.testUiStateObserver = null
|
BoardDetailActivity.testUiStateObserver = null
|
||||||
|
MainActivity.dependencies.clear()
|
||||||
originalLocale?.let { Locale.setDefault(it) }
|
originalLocale?.let { Locale.setDefault(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,6 +263,59 @@ class BoardDetailFlowTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun selectionActionIconsMatchExpectedResources() {
|
||||||
|
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
|
||||||
|
assertNotNull(menu.findItem(R.id.actionSelectAll)?.icon)
|
||||||
|
assertNotNull(menu.findItem(R.id.actionMoveCards)?.icon)
|
||||||
|
assertNotNull(menu.findItem(R.id.actionDeleteCards)?.icon)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
AppCompatResources.getDrawable(activity, R.drawable.ic_select_all_grid_24)?.constantState,
|
||||||
|
menu.findItem(R.id.actionSelectAll)?.icon?.constantState,
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
AppCompatResources.getDrawable(activity, R.drawable.ic_move_cards_horizontal_24)?.constantState,
|
||||||
|
menu.findItem(R.id.actionMoveCards)?.icon?.constantState,
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
AppCompatResources.getDrawable(activity, R.drawable.ic_delete_24)?.constantState,
|
||||||
|
menu.findItem(R.id.actionDeleteCards)?.icon?.constantState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun missingBoardIdShowsBlockingDialogAndFinishes() {
|
||||||
|
val scenario = launchBoardDetail(boardId = null)
|
||||||
|
|
||||||
|
onView(withText(R.string.board_detail_unable_to_open_board)).inRoot(isDialog()).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.ok)).inRoot(isDialog()).perform(click())
|
||||||
|
scenario.onActivity { activity ->
|
||||||
|
assertTrue(activity.isFinishing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun missingSessionShowsBlockingDialogAndFinishes() {
|
||||||
|
BoardDetailActivity.testDataSourceFactory = null
|
||||||
|
MainActivity.dependencies.sessionStoreFactory = { InMemorySessionStore(null) }
|
||||||
|
MainActivity.dependencies.apiKeyStoreFactory = { InMemoryApiKeyStore(null) }
|
||||||
|
|
||||||
|
val scenario = launchBoardDetail()
|
||||||
|
|
||||||
|
onView(withText(R.string.board_detail_session_expired)).inRoot(isDialog()).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.ok)).inRoot(isDialog()).perform(click())
|
||||||
|
scenario.onActivity { activity ->
|
||||||
|
assertTrue(activity.isFinishing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun moveDialogShowsListSelector() {
|
fun moveDialogShowsListSelector() {
|
||||||
defaultDataSource.currentDetail = detailTwoLists()
|
defaultDataSource.currentDetail = detailTwoLists()
|
||||||
@@ -341,6 +401,7 @@ class BoardDetailFlowTest {
|
|||||||
defaultDataSource.moveCalls == 1 && observedStates.lastOrNull()?.isMutating == false
|
defaultDataSource.moveCalls == 1 && observedStates.lastOrNull()?.isMutating == false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onView(withText("Some cards could not be moved. Please try again.")).check(matches(isDisplayed()))
|
||||||
val last = observedStates.last()
|
val last = observedStates.last()
|
||||||
assertEquals(setOf("card-2"), last.selectedCardIds)
|
assertEquals(setOf("card-2"), last.selectedCardIds)
|
||||||
}
|
}
|
||||||
@@ -360,6 +421,7 @@ class BoardDetailFlowTest {
|
|||||||
defaultDataSource.moveCalls == 1 && observedStates.lastOrNull()?.isMutating == false
|
defaultDataSource.moveCalls == 1 && observedStates.lastOrNull()?.isMutating == false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onView(withText("Move failed")).check(matches(isDisplayed()))
|
||||||
val last = observedStates.last()
|
val last = observedStates.last()
|
||||||
assertEquals(setOf("card-1"), last.selectedCardIds)
|
assertEquals(setOf("card-1"), last.selectedCardIds)
|
||||||
}
|
}
|
||||||
@@ -412,6 +474,7 @@ class BoardDetailFlowTest {
|
|||||||
defaultDataSource.deleteCalls == 1 && observedStates.lastOrNull()?.isMutating == false
|
defaultDataSource.deleteCalls == 1 && observedStates.lastOrNull()?.isMutating == false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onView(withText("Some cards could not be deleted. Please try again.")).check(matches(isDisplayed()))
|
||||||
val last = observedStates.last()
|
val last = observedStates.last()
|
||||||
assertEquals(setOf("card-2"), last.selectedCardIds)
|
assertEquals(setOf("card-2"), last.selectedCardIds)
|
||||||
}
|
}
|
||||||
@@ -430,6 +493,7 @@ class BoardDetailFlowTest {
|
|||||||
defaultDataSource.deleteCalls == 1 && observedStates.lastOrNull()?.isMutating == false
|
defaultDataSource.deleteCalls == 1 && observedStates.lastOrNull()?.isMutating == false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onView(withText("Delete failed")).check(matches(isDisplayed()))
|
||||||
val last = observedStates.last()
|
val last = observedStates.last()
|
||||||
assertEquals(setOf("card-1"), last.selectedCardIds)
|
assertEquals(setOf("card-1"), last.selectedCardIds)
|
||||||
}
|
}
|
||||||
@@ -480,12 +544,15 @@ class BoardDetailFlowTest {
|
|||||||
Intents.intended(hasExtra(CardDetailPlaceholderActivity.EXTRA_CARD_TITLE, expectedFallback))
|
Intents.intended(hasExtra(CardDetailPlaceholderActivity.EXTRA_CARD_TITLE, expectedFallback))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchBoardDetail(): ActivityScenario<BoardDetailActivity> {
|
private fun launchBoardDetail(boardId: String? = "board-1"): ActivityScenario<BoardDetailActivity> {
|
||||||
val intent = Intent(
|
val intent = Intent(
|
||||||
androidx.test.core.app.ApplicationProvider.getApplicationContext(),
|
androidx.test.core.app.ApplicationProvider.getApplicationContext(),
|
||||||
BoardDetailActivity::class.java,
|
BoardDetailActivity::class.java,
|
||||||
).putExtra(BoardDetailActivity.EXTRA_BOARD_ID, "board-1")
|
)
|
||||||
.putExtra(BoardDetailActivity.EXTRA_BOARD_TITLE, "Board")
|
if (boardId != null) {
|
||||||
|
intent.putExtra(BoardDetailActivity.EXTRA_BOARD_ID, boardId)
|
||||||
|
}
|
||||||
|
intent.putExtra(BoardDetailActivity.EXTRA_BOARD_TITLE, "Board")
|
||||||
return ActivityScenario.launch(intent)
|
return ActivityScenario.launch(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,6 +678,44 @@ class BoardDetailFlowTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class InMemorySessionStore(
|
||||||
|
private var baseUrl: String?,
|
||||||
|
) : SessionStore {
|
||||||
|
override fun getBaseUrl(): String? = baseUrl
|
||||||
|
|
||||||
|
override fun saveBaseUrl(url: String) {
|
||||||
|
baseUrl = url
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearBaseUrl() {
|
||||||
|
baseUrl = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getWorkspaceId(): String? = "ws-1"
|
||||||
|
|
||||||
|
override fun saveWorkspaceId(workspaceId: String) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearWorkspaceId() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InMemoryApiKeyStore(
|
||||||
|
private var key: String?,
|
||||||
|
) : ApiKeyStore {
|
||||||
|
override suspend fun saveApiKey(baseUrl: String, apiKey: String): Result<Unit> {
|
||||||
|
key = apiKey
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getApiKey(baseUrl: String): Result<String?> = Result.success(key)
|
||||||
|
|
||||||
|
override suspend fun invalidateApiKey(baseUrl: String): Result<Unit> {
|
||||||
|
key = null
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
fun detailOneList(): BoardDetail {
|
fun detailOneList(): BoardDetail {
|
||||||
return BoardDetail(
|
return BoardDetail(
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class BoardDetailActivity : AppCompatActivity() {
|
|||||||
private var deleteSecondConfirmationDialog: AlertDialog? = null
|
private var deleteSecondConfirmationDialog: AlertDialog? = null
|
||||||
private var dismissMoveDialogWhenMutationEnds: Boolean = false
|
private var dismissMoveDialogWhenMutationEnds: Boolean = false
|
||||||
private var dismissDeleteDialogWhenMutationEnds: Boolean = false
|
private var dismissDeleteDialogWhenMutationEnds: Boolean = false
|
||||||
|
private var hasShownBlockingStartupError: Boolean = false
|
||||||
|
|
||||||
private lateinit var pagerAdapter: BoardListsPagerAdapter
|
private lateinit var pagerAdapter: BoardListsPagerAdapter
|
||||||
|
|
||||||
@@ -90,6 +91,11 @@ class BoardDetailActivity : AppCompatActivity() {
|
|||||||
setupPager()
|
setupPager()
|
||||||
observeViewModel()
|
observeViewModel()
|
||||||
|
|
||||||
|
if (boardId.isBlank()) {
|
||||||
|
showBlockingStartupErrorAndFinish(getString(R.string.board_detail_unable_to_open_board))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.loadBoardDetail()
|
viewModel.loadBoardDetail()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,6 +211,15 @@ class BoardDetailActivity : AppCompatActivity() {
|
|||||||
testUiStateObserver?.invoke(state)
|
testUiStateObserver?.invoke(state)
|
||||||
supportActionBar?.title = state.boardDetail?.title ?: intent.getStringExtra(EXTRA_BOARD_TITLE).orEmpty()
|
supportActionBar?.title = state.boardDetail?.title ?: intent.getStringExtra(EXTRA_BOARD_TITLE).orEmpty()
|
||||||
|
|
||||||
|
if (
|
||||||
|
!hasShownBlockingStartupError &&
|
||||||
|
state.boardDetail == null &&
|
||||||
|
state.fullScreenErrorMessage == BoardDetailRepository.MISSING_SESSION_MESSAGE
|
||||||
|
) {
|
||||||
|
showBlockingStartupErrorAndFinish(getString(R.string.board_detail_session_expired))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
fullScreenErrorContainer.visibility = if (state.fullScreenErrorMessage != null && state.boardDetail == null) {
|
fullScreenErrorContainer.visibility = if (state.fullScreenErrorMessage != null && state.boardDetail == null) {
|
||||||
fullScreenErrorText.text = state.fullScreenErrorMessage
|
fullScreenErrorText.text = state.fullScreenErrorMessage
|
||||||
View.VISIBLE
|
View.VISIBLE
|
||||||
@@ -248,6 +263,17 @@ class BoardDetailActivity : AppCompatActivity() {
|
|||||||
renderOpenDialogs(state)
|
renderOpenDialogs(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showBlockingStartupErrorAndFinish(message: String) {
|
||||||
|
hasShownBlockingStartupError = true
|
||||||
|
MaterialAlertDialogBuilder(this)
|
||||||
|
.setMessage(message)
|
||||||
|
.setCancelable(false)
|
||||||
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
private fun renderOpenDialogs(state: BoardDetailUiState) {
|
private fun renderOpenDialogs(state: BoardDetailUiState) {
|
||||||
val activeMoveDialog = moveDialog
|
val activeMoveDialog = moveDialog
|
||||||
if (activeMoveDialog != null) {
|
if (activeMoveDialog != null) {
|
||||||
@@ -350,7 +376,8 @@ class BoardDetailActivity : AppCompatActivity() {
|
|||||||
.create()
|
.create()
|
||||||
dialog.setOnShowListener {
|
dialog.setOnShowListener {
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener {
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener {
|
||||||
val targetId = lists[selectedIndex].id
|
val currentLists = viewModel.uiState.value.boardDetail?.lists.orEmpty()
|
||||||
|
val targetId = currentLists.getOrNull(selectedIndex)?.id ?: return@setOnClickListener
|
||||||
dismissMoveDialogWhenMutationEnds = true
|
dismissMoveDialogWhenMutationEnds = true
|
||||||
viewModel.moveSelectedCards(targetId)
|
viewModel.moveSelectedCards(targetId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ class BoardDetailRepository(
|
|||||||
private val apiClient: KanbnApiClient,
|
private val apiClient: KanbnApiClient,
|
||||||
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||||
) {
|
) {
|
||||||
|
companion object {
|
||||||
|
const val MISSING_SESSION_MESSAGE = "Missing session. Please sign in again."
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getBoardDetail(boardId: String): BoardsApiResult<BoardDetail> {
|
suspend fun getBoardDetail(boardId: String): BoardsApiResult<BoardDetail> {
|
||||||
val normalizedBoardId = boardId.trim()
|
val normalizedBoardId = boardId.trim()
|
||||||
if (normalizedBoardId.isBlank()) {
|
if (normalizedBoardId.isBlank()) {
|
||||||
@@ -154,11 +158,11 @@ class BoardDetailRepository(
|
|||||||
|
|
||||||
private suspend fun session(): BoardsApiResult<SessionSnapshot> {
|
private suspend fun session(): BoardsApiResult<SessionSnapshot> {
|
||||||
val baseUrl = sessionStore.getBaseUrl()?.takeIf { it.isNotBlank() }
|
val baseUrl = sessionStore.getBaseUrl()?.takeIf { it.isNotBlank() }
|
||||||
?: return BoardsApiResult.Failure("Missing session. Please sign in again.")
|
?: return BoardsApiResult.Failure(MISSING_SESSION_MESSAGE)
|
||||||
val apiKey = withContext(ioDispatcher) {
|
val apiKey = withContext(ioDispatcher) {
|
||||||
apiKeyStore.getApiKey(baseUrl)
|
apiKeyStore.getApiKey(baseUrl)
|
||||||
}.getOrNull()?.takeIf { it.isNotBlank() }
|
}.getOrNull()?.takeIf { it.isNotBlank() }
|
||||||
?: return BoardsApiResult.Failure("Missing session. Please sign in again.")
|
?: return BoardsApiResult.Failure(MISSING_SESSION_MESSAGE)
|
||||||
|
|
||||||
val workspaceId = when (val workspaceResult = resolveWorkspaceId(baseUrl = baseUrl, apiKey = apiKey)) {
|
val workspaceId = when (val workspaceResult = resolveWorkspaceId(baseUrl = baseUrl, apiKey = apiKey)) {
|
||||||
is BoardsApiResult.Success -> workspaceResult.value
|
is BoardsApiResult.Success -> workspaceResult.value
|
||||||
|
|||||||
12
app/src/main/res/drawable/ic_delete_24.xml
Normal file
12
app/src/main/res/drawable/ic_delete_24.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M9,3h6l1,1h4v2h-16v-2h4zM6,8h12l-1,12h-10zM9,10v8h2v-8zM13,10v8h2v-8z" />
|
||||||
|
|
||||||
|
</vector>
|
||||||
12
app/src/main/res/drawable/ic_move_cards_horizontal_24.xml
Normal file
12
app/src/main/res/drawable/ic_move_cards_horizontal_24.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M7.5,6l-4.5,6l4.5,6v-4h9v4l4.5,-6l-4.5,-6v4h-9z" />
|
||||||
|
|
||||||
|
</vector>
|
||||||
12
app/src/main/res/drawable/ic_select_all_grid_24.xml
Normal file
12
app/src/main/res/drawable/ic_select_all_grid_24.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M3,3h4v4h-4zM8.5,3h4v4h-4zM14,3h4v4h-4zM19.5,3h1.5v4h-1.5zM3,8.5h4v4h-4zM8.5,8.5h4v4h-4zM14,8.5h4v4h-4zM19.5,8.5h1.5v4h-1.5zM3,14h4v4h-4zM8.5,14h4v4h-4zM14,14h4v4h-4zM19.5,14h1.5v4h-1.5zM3,19.5h4v1.5h-4zM8.5,19.5h4v1.5h-4zM14,19.5h4v1.5h-4zM19.5,19.5h1.5v1.5h-1.5z" />
|
||||||
|
|
||||||
|
</vector>
|
||||||
@@ -4,19 +4,19 @@
|
|||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/actionSelectAll"
|
android:id="@+id/actionSelectAll"
|
||||||
android:icon="@android:drawable/ic_menu_agenda"
|
android:icon="@drawable/ic_select_all_grid_24"
|
||||||
android:title="@string/select_all"
|
android:title="@string/select_all"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="always" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/actionMoveCards"
|
android:id="@+id/actionMoveCards"
|
||||||
android:icon="@android:drawable/ic_menu_directions"
|
android:icon="@drawable/ic_move_cards_horizontal_24"
|
||||||
android:title="@string/move_cards"
|
android:title="@string/move_cards"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="always" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/actionDeleteCards"
|
android:id="@+id/actionDeleteCards"
|
||||||
android:icon="@android:drawable/ic_menu_delete"
|
android:icon="@drawable/ic_delete_24"
|
||||||
android:title="@string/delete_cards"
|
android:title="@string/delete_cards"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="always" />
|
||||||
</menu>
|
</menu>
|
||||||
|
|||||||
@@ -48,4 +48,6 @@
|
|||||||
<string name="card_detail_placeholder_title">%1$s\n(id: %2$s)</string>
|
<string name="card_detail_placeholder_title">%1$s\n(id: %2$s)</string>
|
||||||
<string name="card_detail_placeholder_fallback_title">Card</string>
|
<string name="card_detail_placeholder_fallback_title">Card</string>
|
||||||
<string name="card_detail_placeholder_subtitle">Card detail view is coming soon.</string>
|
<string name="card_detail_placeholder_subtitle">Card detail view is coming soon.</string>
|
||||||
|
<string name="board_detail_unable_to_open_board">Unable to open board.</string>
|
||||||
|
<string name="board_detail_session_expired">Session expired. Please sign in again.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user