For #12831 - Disable SwipeRefreshLayout while swiping a bookmark.
parent
5318d64911
commit
30c7b5ea5e
|
@ -45,6 +45,8 @@ interface BookmarkController {
|
||||||
fun handleBookmarkFolderDeletion(nodes: Set<BookmarkNode>)
|
fun handleBookmarkFolderDeletion(nodes: Set<BookmarkNode>)
|
||||||
fun handleRequestSync()
|
fun handleRequestSync()
|
||||||
fun handleBackPressed()
|
fun handleBackPressed()
|
||||||
|
fun handleStartSwipingItem()
|
||||||
|
fun handleStopSwipingItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
|
@ -169,6 +171,14 @@ class DefaultBookmarkController(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun handleStartSwipingItem() {
|
||||||
|
store.dispatch(BookmarkFragmentAction.SwipeRefreshAvailabilityChanged(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleStopSwipingItem() {
|
||||||
|
store.dispatch(BookmarkFragmentAction.SwipeRefreshAvailabilityChanged(true))
|
||||||
|
}
|
||||||
|
|
||||||
private fun openInNewTab(
|
private fun openInNewTab(
|
||||||
searchTermOrURL: String,
|
searchTermOrURL: String,
|
||||||
newTab: Boolean,
|
newTab: Boolean,
|
||||||
|
|
|
@ -120,4 +120,12 @@ class BookmarkFragmentInteractor(
|
||||||
override fun onRequestSync() {
|
override fun onRequestSync() {
|
||||||
bookmarksController.handleRequestSync()
|
bookmarksController.handleRequestSync()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStartSwipingItem() {
|
||||||
|
bookmarksController.handleStartSwipingItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStopSwipingItem() {
|
||||||
|
bookmarksController.handleStopSwipingItem()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,12 +23,14 @@ class BookmarkFragmentStore(
|
||||||
* @property guidBackstack A set of guids for bookmark nodes we have visited. Used to traverse back
|
* @property guidBackstack A set of guids for bookmark nodes we have visited. Used to traverse back
|
||||||
* up the tree after a sync.
|
* up the tree after a sync.
|
||||||
* @property isLoading true if bookmarks are still being loaded from disk
|
* @property isLoading true if bookmarks are still being loaded from disk
|
||||||
|
* @property isSwipeToRefreshEnabled true if swipe to refresh should be enabled
|
||||||
*/
|
*/
|
||||||
data class BookmarkFragmentState(
|
data class BookmarkFragmentState(
|
||||||
val tree: BookmarkNode?,
|
val tree: BookmarkNode?,
|
||||||
val mode: Mode = Mode.Normal(),
|
val mode: Mode = Mode.Normal(),
|
||||||
val guidBackstack: List<String> = emptyList(),
|
val guidBackstack: List<String> = emptyList(),
|
||||||
val isLoading: Boolean = true
|
val isLoading: Boolean = true,
|
||||||
|
val isSwipeToRefreshEnabled: Boolean = true
|
||||||
) : State {
|
) : State {
|
||||||
sealed class Mode {
|
sealed class Mode {
|
||||||
open val selectedItems = emptySet<BookmarkNode>()
|
open val selectedItems = emptySet<BookmarkNode>()
|
||||||
|
@ -49,6 +51,7 @@ sealed class BookmarkFragmentAction : Action {
|
||||||
object DeselectAll : BookmarkFragmentAction()
|
object DeselectAll : BookmarkFragmentAction()
|
||||||
object StartSync : BookmarkFragmentAction()
|
object StartSync : BookmarkFragmentAction()
|
||||||
object FinishSync : BookmarkFragmentAction()
|
object FinishSync : BookmarkFragmentAction()
|
||||||
|
data class SwipeRefreshAvailabilityChanged(val enabled: Boolean) : BookmarkFragmentAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,31 +74,37 @@ private fun bookmarkFragmentStateReducer(
|
||||||
} + action.tree.guid
|
} + action.tree.guid
|
||||||
|
|
||||||
val items = state.mode.selectedItems.filter { it in action.tree }
|
val items = state.mode.selectedItems.filter { it in action.tree }
|
||||||
|
val mode = when {
|
||||||
|
state.mode is BookmarkFragmentState.Mode.Syncing -> {
|
||||||
|
BookmarkFragmentState.Mode.Syncing
|
||||||
|
}
|
||||||
|
items.isEmpty() -> {
|
||||||
|
BookmarkFragmentState.Mode.Normal(shouldShowMenu(action.tree.guid))
|
||||||
|
}
|
||||||
|
else -> BookmarkFragmentState.Mode.Selecting(items.toSet())
|
||||||
|
}
|
||||||
state.copy(
|
state.copy(
|
||||||
tree = action.tree,
|
tree = action.tree,
|
||||||
mode = when {
|
mode = mode,
|
||||||
state.mode is BookmarkFragmentState.Mode.Syncing -> {
|
|
||||||
BookmarkFragmentState.Mode.Syncing
|
|
||||||
}
|
|
||||||
items.isEmpty() -> {
|
|
||||||
BookmarkFragmentState.Mode.Normal(shouldShowMenu(action.tree.guid))
|
|
||||||
}
|
|
||||||
else -> BookmarkFragmentState.Mode.Selecting(items.toSet())
|
|
||||||
},
|
|
||||||
guidBackstack = backstack,
|
guidBackstack = backstack,
|
||||||
isLoading = false
|
isLoading = false,
|
||||||
|
isSwipeToRefreshEnabled = mode !is BookmarkFragmentState.Mode.Selecting
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is BookmarkFragmentAction.Select ->
|
is BookmarkFragmentAction.Select -> state.copy(
|
||||||
state.copy(mode = BookmarkFragmentState.Mode.Selecting(state.mode.selectedItems + action.item))
|
mode = BookmarkFragmentState.Mode.Selecting(state.mode.selectedItems + action.item),
|
||||||
|
isSwipeToRefreshEnabled = false
|
||||||
|
)
|
||||||
is BookmarkFragmentAction.Deselect -> {
|
is BookmarkFragmentAction.Deselect -> {
|
||||||
val items = state.mode.selectedItems - action.item
|
val items = state.mode.selectedItems - action.item
|
||||||
|
val mode = if (items.isEmpty()) {
|
||||||
|
BookmarkFragmentState.Mode.Normal()
|
||||||
|
} else {
|
||||||
|
BookmarkFragmentState.Mode.Selecting(items)
|
||||||
|
}
|
||||||
state.copy(
|
state.copy(
|
||||||
mode = if (items.isEmpty()) {
|
mode = mode,
|
||||||
BookmarkFragmentState.Mode.Normal()
|
isSwipeToRefreshEnabled = mode !is BookmarkFragmentState.Mode.Selecting
|
||||||
} else {
|
|
||||||
BookmarkFragmentState.Mode.Selecting(items)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is BookmarkFragmentAction.DeselectAll ->
|
is BookmarkFragmentAction.DeselectAll ->
|
||||||
|
@ -104,18 +113,22 @@ private fun bookmarkFragmentStateReducer(
|
||||||
BookmarkFragmentState.Mode.Syncing
|
BookmarkFragmentState.Mode.Syncing
|
||||||
} else {
|
} else {
|
||||||
BookmarkFragmentState.Mode.Normal()
|
BookmarkFragmentState.Mode.Normal()
|
||||||
}
|
},
|
||||||
)
|
isSwipeToRefreshEnabled = true
|
||||||
is BookmarkFragmentAction.StartSync ->
|
|
||||||
state.copy(
|
|
||||||
mode = BookmarkFragmentState.Mode.Syncing
|
|
||||||
)
|
|
||||||
is BookmarkFragmentAction.FinishSync ->
|
|
||||||
state.copy(
|
|
||||||
mode = BookmarkFragmentState.Mode.Normal(
|
|
||||||
showMenu = shouldShowMenu(state.tree?.guid)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
is BookmarkFragmentAction.StartSync -> state.copy(
|
||||||
|
mode = BookmarkFragmentState.Mode.Syncing,
|
||||||
|
isSwipeToRefreshEnabled = true
|
||||||
|
)
|
||||||
|
is BookmarkFragmentAction.FinishSync -> state.copy(
|
||||||
|
mode = BookmarkFragmentState.Mode.Normal(
|
||||||
|
showMenu = shouldShowMenu(state.tree?.guid)
|
||||||
|
),
|
||||||
|
isSwipeToRefreshEnabled = true
|
||||||
|
)
|
||||||
|
is BookmarkFragmentAction.SwipeRefreshAvailabilityChanged -> state.copy(
|
||||||
|
isSwipeToRefreshEnabled = action.enabled && state.mode !is BookmarkFragmentState.Mode.Selecting
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,15 @@ class BookmarkTouchCallback(
|
||||||
target: RecyclerView.ViewHolder
|
target: RecyclerView.ViewHolder
|
||||||
): Boolean = false
|
): Boolean = false
|
||||||
|
|
||||||
|
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
||||||
|
super.onSelectedChanged(viewHolder, actionState)
|
||||||
|
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
|
||||||
|
interactor.onStartSwipingItem()
|
||||||
|
} else {
|
||||||
|
interactor.onStopSwipingItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun setBounds(
|
private fun setBounds(
|
||||||
background: Drawable,
|
background: Drawable,
|
||||||
backgroundBounds: Rect,
|
backgroundBounds: Rect,
|
||||||
|
|
|
@ -98,6 +98,16 @@ interface BookmarkViewInteractor : SelectionInteractor<BookmarkNode> {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
fun onRequestSync()
|
fun onRequestSync()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the start of a swipe on a bookmark.
|
||||||
|
*/
|
||||||
|
fun onStartSwipingItem()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the end of a swipe on a bookmark.
|
||||||
|
*/
|
||||||
|
fun onStopSwipingItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
class BookmarkView(
|
class BookmarkView(
|
||||||
|
@ -152,8 +162,7 @@ class BookmarkView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
view.bookmarks_progress_bar.isVisible = state.isLoading
|
view.bookmarks_progress_bar.isVisible = state.isLoading
|
||||||
view.swipe_refresh.isEnabled =
|
view.swipe_refresh.isEnabled = state.isSwipeToRefreshEnabled
|
||||||
state.mode is BookmarkFragmentState.Mode.Normal || state.mode is BookmarkFragmentState.Mode.Syncing
|
|
||||||
view.swipe_refresh.isRefreshing = state.mode is BookmarkFragmentState.Mode.Syncing
|
view.swipe_refresh.isRefreshing = state.mode is BookmarkFragmentState.Mode.Syncing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -337,4 +337,22 @@ class BookmarkControllerTest {
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `handleStartSwipingItem disables swipe to refresh`() {
|
||||||
|
controller.handleStartSwipingItem()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
bookmarkStore.dispatch(BookmarkFragmentAction.SwipeRefreshAvailabilityChanged(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `handleStopSwipingItem attempts to enable swipe to refresh`() {
|
||||||
|
controller.handleStopSwipingItem()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
bookmarkStore.dispatch(BookmarkFragmentAction.SwipeRefreshAvailabilityChanged(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,4 +210,22 @@ class BookmarkFragmentInteractorTest {
|
||||||
bookmarkController.handleRequestSync()
|
bookmarkController.handleRequestSync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `start swiping an item`() {
|
||||||
|
interactor.onStartSwipingItem()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
bookmarkController.handleStartSwipingItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `stop swiping an item`() {
|
||||||
|
interactor.onStopSwipingItem()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
bookmarkController.handleStopSwipingItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,32 +80,63 @@ class BookmarkFragmentStoreTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ensure selected items remain selected after a tree change`() = runBlocking {
|
fun `ensure selected items remain selected after a tree change`() = runBlocking {
|
||||||
val initialState = BookmarkFragmentState(tree, BookmarkFragmentState.Mode.Selecting(setOf(item, subfolder)))
|
val initialState = BookmarkFragmentState(
|
||||||
|
tree,
|
||||||
|
BookmarkFragmentState.Mode.Selecting(setOf(item, subfolder)),
|
||||||
|
isLoading = false,
|
||||||
|
isSwipeToRefreshEnabled = false
|
||||||
|
)
|
||||||
val store = BookmarkFragmentStore(initialState)
|
val store = BookmarkFragmentStore(initialState)
|
||||||
|
|
||||||
store.dispatch(BookmarkFragmentAction.Change(newTree)).join()
|
store.dispatch(BookmarkFragmentAction.Change(newTree)).join()
|
||||||
|
|
||||||
assertEquals(store.state.tree, newTree)
|
assertEquals(
|
||||||
assertEquals(store.state.mode, BookmarkFragmentState.Mode.Selecting(setOf(subfolder)))
|
store.state,
|
||||||
|
BookmarkFragmentState(
|
||||||
|
newTree,
|
||||||
|
BookmarkFragmentState.Mode.Selecting(setOf(subfolder)),
|
||||||
|
guidBackstack = listOf(tree.guid),
|
||||||
|
isLoading = false,
|
||||||
|
isSwipeToRefreshEnabled = false
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `select and deselect bookmarks changes the mode`() = runBlocking {
|
fun `select and deselect a single bookmark changes the mode and swipe to refresh state`() = runBlocking {
|
||||||
val initialState = BookmarkFragmentState(tree)
|
val initialState = BookmarkFragmentState(tree)
|
||||||
val store = BookmarkFragmentStore(initialState)
|
val store = BookmarkFragmentStore(initialState)
|
||||||
|
|
||||||
store.dispatch(BookmarkFragmentAction.Select(childItem)).join()
|
store.dispatch(BookmarkFragmentAction.Select(childItem)).join()
|
||||||
|
|
||||||
assertEquals(store.state, BookmarkFragmentState(tree, BookmarkFragmentState.Mode.Selecting(setOf(childItem))))
|
assertEquals(
|
||||||
|
store.state,
|
||||||
|
BookmarkFragmentState(
|
||||||
|
tree,
|
||||||
|
BookmarkFragmentState.Mode.Selecting(setOf(childItem)),
|
||||||
|
isSwipeToRefreshEnabled = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
store.dispatch(BookmarkFragmentAction.Deselect(childItem)).join()
|
store.dispatch(BookmarkFragmentAction.Deselect(childItem)).join()
|
||||||
|
|
||||||
assertEquals(store.state, BookmarkFragmentState(tree, BookmarkFragmentState.Mode.Normal()))
|
assertEquals(
|
||||||
|
store.state,
|
||||||
|
BookmarkFragmentState(
|
||||||
|
tree,
|
||||||
|
BookmarkFragmentState.Mode.Normal(),
|
||||||
|
isSwipeToRefreshEnabled = true
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `selecting the same item twice does nothing`() = runBlocking {
|
fun `selecting the same item twice does nothing`() = runBlocking {
|
||||||
val initialState = BookmarkFragmentState(tree, BookmarkFragmentState.Mode.Selecting(setOf(item, subfolder)))
|
val initialState = BookmarkFragmentState(
|
||||||
|
tree,
|
||||||
|
BookmarkFragmentState.Mode.Selecting(setOf(item, subfolder)),
|
||||||
|
isSwipeToRefreshEnabled = false
|
||||||
|
)
|
||||||
val store = BookmarkFragmentStore(initialState)
|
val store = BookmarkFragmentStore(initialState)
|
||||||
|
|
||||||
store.dispatch(BookmarkFragmentAction.Select(item)).join()
|
store.dispatch(BookmarkFragmentAction.Select(item)).join()
|
||||||
|
@ -115,7 +146,11 @@ class BookmarkFragmentStoreTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `deselecting an unselected bookmark does nothing`() = runBlocking {
|
fun `deselecting an unselected bookmark does nothing`() = runBlocking {
|
||||||
val initialState = BookmarkFragmentState(tree, BookmarkFragmentState.Mode.Selecting(setOf(childItem)))
|
val initialState = BookmarkFragmentState(
|
||||||
|
tree,
|
||||||
|
BookmarkFragmentState.Mode.Selecting(setOf(childItem)),
|
||||||
|
isSwipeToRefreshEnabled = false
|
||||||
|
)
|
||||||
val store = BookmarkFragmentStore(initialState)
|
val store = BookmarkFragmentStore(initialState)
|
||||||
|
|
||||||
store.dispatch(BookmarkFragmentAction.Deselect(item)).join()
|
store.dispatch(BookmarkFragmentAction.Deselect(item)).join()
|
||||||
|
@ -134,14 +169,25 @@ class BookmarkFragmentStoreTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `deselect all bookmarks changes the mode`() = runBlocking {
|
fun `deselect all bookmarks changes the mode and updates swipe to refresh state`() =
|
||||||
val initialState = BookmarkFragmentState(tree, BookmarkFragmentState.Mode.Selecting(setOf(item, childItem)))
|
runBlocking {
|
||||||
val store = BookmarkFragmentStore(initialState)
|
val initialState = BookmarkFragmentState(
|
||||||
|
tree,
|
||||||
|
BookmarkFragmentState.Mode.Selecting(setOf(item, childItem)),
|
||||||
|
isSwipeToRefreshEnabled = false
|
||||||
|
)
|
||||||
|
val store = BookmarkFragmentStore(initialState)
|
||||||
|
|
||||||
store.dispatch(BookmarkFragmentAction.DeselectAll).join()
|
store.dispatch(BookmarkFragmentAction.DeselectAll).join()
|
||||||
|
|
||||||
assertEquals(store.state, initialState.copy(mode = BookmarkFragmentState.Mode.Normal()))
|
assertEquals(
|
||||||
}
|
store.state,
|
||||||
|
initialState.copy(
|
||||||
|
mode = BookmarkFragmentState.Mode.Normal(),
|
||||||
|
isSwipeToRefreshEnabled = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `deselect all bookmarks when none are selected`() = runBlocking {
|
fun `deselect all bookmarks when none are selected`() = runBlocking {
|
||||||
|
@ -214,6 +260,45 @@ class BookmarkFragmentStoreTest {
|
||||||
assertEquals(BookmarkFragmentState.Mode.Syncing, store.state.mode)
|
assertEquals(BookmarkFragmentState.Mode.Syncing, store.state.mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `enabling swipe to refresh in Normal mode works`() = runBlocking {
|
||||||
|
val initialState = BookmarkFragmentState(
|
||||||
|
tree,
|
||||||
|
BookmarkFragmentState.Mode.Normal(),
|
||||||
|
isSwipeToRefreshEnabled = false
|
||||||
|
)
|
||||||
|
val store = BookmarkFragmentStore(initialState)
|
||||||
|
|
||||||
|
store.dispatch(BookmarkFragmentAction.SwipeRefreshAvailabilityChanged(true)).join()
|
||||||
|
assertEquals(true, store.state.isSwipeToRefreshEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `enabling swipe to refresh in Syncing mode works`() = runBlocking {
|
||||||
|
val initialState = BookmarkFragmentState(
|
||||||
|
tree,
|
||||||
|
BookmarkFragmentState.Mode.Syncing,
|
||||||
|
isSwipeToRefreshEnabled = false
|
||||||
|
)
|
||||||
|
val store = BookmarkFragmentStore(initialState)
|
||||||
|
|
||||||
|
store.dispatch(BookmarkFragmentAction.SwipeRefreshAvailabilityChanged(true)).join()
|
||||||
|
assertEquals(true, store.state.isSwipeToRefreshEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `enabling swipe to refresh in Selecting mode does not work`() = runBlocking {
|
||||||
|
val initialState = BookmarkFragmentState(
|
||||||
|
tree,
|
||||||
|
BookmarkFragmentState.Mode.Selecting(emptySet()),
|
||||||
|
isSwipeToRefreshEnabled = false
|
||||||
|
)
|
||||||
|
val store = BookmarkFragmentStore(initialState)
|
||||||
|
|
||||||
|
store.dispatch(BookmarkFragmentAction.SwipeRefreshAvailabilityChanged(true)).join()
|
||||||
|
assertEquals(false, store.state.isSwipeToRefreshEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
private val item = BookmarkNode(BookmarkNodeType.ITEM, "456", "123", 0, "Mozilla", "http://mozilla.org", null)
|
private val item = BookmarkNode(BookmarkNodeType.ITEM, "456", "123", 0, "Mozilla", "http://mozilla.org", null)
|
||||||
private val separator = BookmarkNode(BookmarkNodeType.SEPARATOR, "789", "123", 1, null, null, null)
|
private val separator = BookmarkNode(BookmarkNodeType.SEPARATOR, "789", "123", 1, null, null, null)
|
||||||
private val subfolder = BookmarkNode(BookmarkNodeType.FOLDER, "987", "123", 0, "Subfolder", null, listOf())
|
private val subfolder = BookmarkNode(BookmarkNodeType.FOLDER, "987", "123", 0, "Subfolder", null, listOf())
|
||||||
|
|
Loading…
Reference in New Issue