1
0
Fork 0

No issue: Move signedIn tracking to BookmarksSharedViewModel (#4755)

master
Tiger Oakes 2019-09-27 08:57:39 -07:00 committed by Sawyer Blatz
parent 7f328a6dc4
commit ee1f040e53
10 changed files with 177 additions and 187 deletions

View File

@ -112,7 +112,7 @@ open class FenixApplication : Application() {
components.core.sessionManager.register(NotificationSessionObserver(this))
if ((System.currentTimeMillis() - this.settings().lastPlacesStorageMaintenance) > ONE_DAY_MILLIS) {
if ((System.currentTimeMillis() - settings().lastPlacesStorageMaintenance) > ONE_DAY_MILLIS) {
runStorageMaintenance()
}
}
@ -123,7 +123,7 @@ open class FenixApplication : Application() {
// run maintenance on one - arbitrarily using bookmarks.
components.core.bookmarksStorage.runMaintenance()
}
this.settings().lastPlacesStorageMaintenance = System.currentTimeMillis()
settings().lastPlacesStorageMaintenance = System.currentTimeMillis()
}
private fun registerRxExceptionHandling() {

View File

@ -13,7 +13,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDirections
@ -29,6 +28,7 @@ import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.withContext
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
@ -49,12 +49,11 @@ import org.mozilla.fenix.ext.urlToTrimmedHost
import org.mozilla.fenix.library.LibraryPageFragment
import org.mozilla.fenix.utils.allowUndo
@SuppressWarnings("TooManyFunctions", "LargeClass")
class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), BackHandler, AccountObserver {
@Suppress("TooManyFunctions", "LargeClass")
class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), BackHandler {
private lateinit var bookmarkStore: BookmarkFragmentStore
private lateinit var bookmarkView: BookmarkView
private lateinit var signInView: SignInView
private lateinit var bookmarkInteractor: BookmarkFragmentInteractor
private val sharedViewModel: BookmarksSharedViewModel by activityViewModels {
@ -62,10 +61,14 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), BackHandler, Accou
}
private val desktopFolders by lazy { DesktopFolders(context!!, showMobileRoot = false) }
var currentRoot: BookmarkNode? = null
lateinit var initialJob: Job
private var pendingBookmarkDeletionJob: (suspend () -> Unit)? = null
private var pendingBookmarksToDelete: MutableSet<BookmarkNode> = mutableSetOf()
private val refreshOnSignInListener = object : AccountObserver {
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
lifecycleScope.launch { refreshBookmarks() }
}
}
private val metrics
get() = context?.components?.analytics?.metrics
@ -92,7 +95,9 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), BackHandler, Accou
)
bookmarkView = BookmarkView(view.bookmarkLayout, bookmarkInteractor)
signInView = SignInView(view.bookmarkLayout, bookmarkInteractor)
val signInView = SignInView(view.bookmarkLayout, findNavController())
sharedViewModel.signedIn.observe(viewLifecycleOwner, signInView)
lifecycle.addObserver(
BookmarkDeselectNavigationListener(
@ -112,11 +117,6 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), BackHandler, Accou
consumeFrom(bookmarkStore) {
bookmarkView.update(it)
}
sharedViewModel.apply {
signedIn.observe(this@BookmarkFragment, Observer<Boolean> {
signInView.update(it)
})
}
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -129,7 +129,10 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), BackHandler, Accou
super.onResume()
(activity as? AppCompatActivity)?.supportActionBar?.show()
checkIfSignedIn()
context?.components?.backgroundServices?.accountManager?.let { accountManager ->
sharedViewModel.observeAccountManager(accountManager, owner = this)
accountManager.register(refreshOnSignInListener, owner = this)
}
val currentGuid = BookmarkFragmentArgs.fromBundle(arguments!!).currentRoot.ifEmpty { BookmarkRoot.Mobile.id }
@ -137,26 +140,20 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), BackHandler, Accou
}
private fun loadInitialBookmarkFolder(currentGuid: String): Job {
return viewLifecycleOwner.lifecycleScope.launch(IO) {
currentRoot = requireContext().bookmarkStorage
.getTree(currentGuid)?.let { desktopFolders.withOptionalDesktopFolders(it) }!!
return viewLifecycleOwner.lifecycleScope.launch(Main) {
val currentRoot = withContext(IO) {
requireContext().bookmarkStorage
.getTree(currentGuid)
?.let { desktopFolders.withOptionalDesktopFolders(it) }!!
}
if (!isActive) return@launch
launch(Main) {
bookmarkInteractor.onBookmarksChanged(currentRoot!!)
if (isActive) {
bookmarkInteractor.onBookmarksChanged(currentRoot)
sharedViewModel.selectedFolder = currentRoot
}
}
}
private fun checkIfSignedIn() {
context?.components?.backgroundServices?.accountManager?.let {
it.register(this, owner = this)
it.authenticatedAccount()?.let { bookmarkInteractor.onSignedIn() }
?: bookmarkInteractor.onSignedOut()
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
when (val mode = bookmarkStore.state.mode) {
BookmarkFragmentState.Mode.Normal -> {
@ -229,17 +226,6 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), BackHandler, Accou
return bookmarkView.onBackPressed()
}
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
bookmarkInteractor.onSignedIn()
lifecycleScope.launch {
refreshBookmarks()
}
}
override fun onLoggedOut() {
bookmarkInteractor.onSignedOut()
}
private suspend fun refreshBookmarks() {
// The bookmark tree in our 'state' can be null - meaning, no bookmark tree has been selected.
// If that's the case, we don't know what node to refresh, and so we bail out.
@ -274,7 +260,7 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), BackHandler, Accou
private fun deleteMulti(selected: Set<BookmarkNode>, eventType: Event = Event.RemoveBookmarks) {
pendingBookmarksToDelete.addAll(selected)
val bookmarkTree = currentRoot!! - pendingBookmarksToDelete
val bookmarkTree = sharedViewModel.selectedFolder!! - pendingBookmarksToDelete
bookmarkInteractor.onBookmarksChanged(bookmarkTree)
val deleteOperation: (suspend () -> Unit) = {

View File

@ -26,7 +26,7 @@ class BookmarkFragmentInteractor(
private val viewModel: BookmarksSharedViewModel,
private val bookmarksController: BookmarkController,
private val metrics: MetricController
) : BookmarkViewInteractor, SignInInteractor {
) : BookmarkViewInteractor {
override fun onBookmarksChanged(node: BookmarkNode) {
bookmarkStore.dispatch(BookmarkFragmentAction.Change(node))
@ -96,18 +96,6 @@ class BookmarkFragmentInteractor(
bookmarksController.handleBackPressed()
}
override fun onSignInPressed() {
bookmarksController.handleSigningIn()
}
override fun onSignedIn() {
viewModel.signedIn.postValue(true)
}
override fun onSignedOut() {
viewModel.signedIn.postValue(false)
}
override fun open(item: BookmarkNode) {
Do exhaustive when (item.type) {
BookmarkNodeType.ITEM -> {

View File

@ -4,11 +4,53 @@
package org.mozilla.fenix.library.bookmarks
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.service.fxa.manager.FxaAccountManager
class BookmarksSharedViewModel : ViewModel() {
var signedIn = MutableLiveData<Boolean>().apply { postValue(true) }
/**
* [ViewModel] that shares data between various bookmarks fragments.
*/
class BookmarksSharedViewModel : ViewModel(), AccountObserver {
private val signedInMutable = MutableLiveData(true)
/**
* Whether or not the user is signed in.
*/
val signedIn: LiveData<Boolean> get() = signedInMutable
/**
* The currently selected bookmark root.
*/
var selectedFolder: BookmarkNode? = null
/**
* Updates the [signedIn] boolean once the account observer sees that the user logged in.
*/
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
signedInMutable.postValue(true)
}
/**
* Updates the [signedIn] boolean once the account observer sees that the user logged out.
*/
override fun onLoggedOut() {
signedInMutable.postValue(false)
}
fun observeAccountManager(accountManager: FxaAccountManager, owner: LifecycleOwner) {
accountManager.register(this, owner = owner)
if (accountManager.authenticatedAccount() != null) {
signedInMutable.postValue(true)
} else {
signedInMutable.postValue(false)
}
}
}

View File

@ -7,20 +7,18 @@ package org.mozilla.fenix.library.bookmarks
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isGone
import androidx.lifecycle.Observer
import androidx.navigation.NavController
import com.google.android.material.button.MaterialButton
import kotlinx.android.extensions.LayoutContainer
import org.mozilla.fenix.R
interface SignInInteractor {
fun onSignInPressed()
fun onSignedIn()
fun onSignedOut()
}
import org.mozilla.fenix.ext.components
class SignInView(
private val container: ViewGroup,
private val interactor: SignInInteractor
) : LayoutContainer {
private val navController: NavController
) : LayoutContainer, Observer<Boolean> {
override val containerView: View?
get() = container
@ -31,11 +29,14 @@ class SignInView(
init {
view.setOnClickListener {
interactor.onSignInPressed()
view.context.components.services.launchPairingSignIn(view.context, navController)
}
}
fun update(signedIn: Boolean) {
view.visibility = if (signedIn) View.GONE else View.VISIBLE
/**
* Hides or shows the sign-in button. Should be called whenever the sign-in state changes.
*/
override fun onChanged(signedIn: Boolean) {
view.isGone = signedIn
}
}

View File

@ -32,6 +32,7 @@ import kotlinx.android.synthetic.main.fragment_edit_bookmark.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.appservices.places.UrlParseFailed
import mozilla.components.concept.storage.BookmarkInfo
import mozilla.components.concept.storage.BookmarkNode
@ -75,40 +76,40 @@ class EditBookmarkFragment : Fragment() {
activity?.supportActionBar?.show()
guidToEdit = EditBookmarkFragmentArgs.fromBundle(arguments!!).guidToEdit
lifecycleScope.launch(IO) {
lifecycleScope.launch(Main) {
val context = requireContext()
bookmarkNode = context.components.core.bookmarksStorage.getTree(guidToEdit)
bookmarkParent = sharedViewModel.selectedFolder
?: bookmarkNode?.parentGuid
?.let { context.components.core.bookmarksStorage.getTree(it) }
?.let { DesktopFolders(context, showMobileRoot = true).withRootTitle(it) }
launch(Main) {
when (bookmarkNode?.type) {
BookmarkNodeType.FOLDER -> {
activity?.title = getString(R.string.edit_bookmark_folder_fragment_title)
bookmarkUrlEdit.visibility = View.GONE
bookmarkUrlLabel.visibility = View.GONE
}
BookmarkNodeType.ITEM -> {
activity?.title = getString(R.string.edit_bookmark_fragment_title)
}
else -> throw IllegalArgumentException()
withContext(IO) {
val bookmarksStorage = context.components.core.bookmarksStorage
bookmarkNode = bookmarksStorage.getTree(guidToEdit)
bookmarkParent = sharedViewModel.selectedFolder
?: bookmarkNode?.parentGuid
?.let { bookmarksStorage.getTree(it) }
?.let { DesktopFolders(context, showMobileRoot = true).withRootTitle(it) }
}
when (bookmarkNode?.type) {
BookmarkNodeType.FOLDER -> {
activity?.title = getString(R.string.edit_bookmark_folder_fragment_title)
bookmarkUrlEdit.visibility = View.GONE
bookmarkUrlLabel.visibility = View.GONE
}
BookmarkNodeType.ITEM -> {
activity?.title = getString(R.string.edit_bookmark_fragment_title)
}
else -> throw IllegalArgumentException()
}
if (bookmarkNode != null) {
bookmarkNameEdit.setText(bookmarkNode!!.title)
bookmarkUrlEdit.setText(bookmarkNode!!.url)
bookmarkNode?.let { bookmarkNode ->
bookmarkNameEdit.setText(bookmarkNode.title)
bookmarkUrlEdit.setText(bookmarkNode.url)
if (sharedViewModel.selectedFolder != null && bookmarkNode?.title != null) {
val bookmarkPair = Pair(bookmarkNode?.title, bookmarkNode?.url)
updateBookmarkNode(bookmarkPair)
}
if (sharedViewModel.selectedFolder != null && bookmarkNode.title != null) {
updateBookmarkNode(bookmarkNode.title to bookmarkNode.url)
}
}
bookmarkParent?.let { node ->
launch(Main) {
bookmarkFolderSelector.text = node.title
bookmarkFolderSelector.setOnClickListener {
sharedViewModel.selectedFolder = null
@ -120,7 +121,6 @@ class EditBookmarkFragment : Fragment() {
}
}
}
}
updateBookmarkFromObservableInput()
}

View File

@ -14,7 +14,6 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
@ -27,9 +26,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.nav
@ -38,16 +34,12 @@ import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel
import org.mozilla.fenix.library.bookmarks.DesktopFolders
import org.mozilla.fenix.library.bookmarks.SignInView
@SuppressWarnings("TooManyFunctions")
class SelectBookmarkFolderFragment : Fragment(), AccountObserver {
class SelectBookmarkFolderFragment : Fragment() {
private val sharedViewModel: BookmarksSharedViewModel by activityViewModels {
ViewModelProvider.NewInstanceFactory() // this is a workaround for #4652
}
private var folderGuid: String? = null
private var bookmarkNode: BookmarkNode? = null
private lateinit var signInView: SignInView
private lateinit var bookmarkInteractor: SelectBookmarkFolderInteractor
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -57,30 +49,19 @@ class SelectBookmarkFolderFragment : Fragment(), AccountObserver {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_select_bookmark_folder, container, false)
bookmarkInteractor = SelectBookmarkFolderInteractor(
context!!,
findNavController(),
sharedViewModel
)
signInView = SignInView(view.selectBookmarkLayout, bookmarkInteractor)
val signInView = SignInView(view.selectBookmarkLayout, findNavController())
sharedViewModel.signedIn.observe(viewLifecycleOwner, signInView)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedViewModel.signedIn.observe(this@SelectBookmarkFolderFragment, Observer<Boolean> {
signInView.update(it)
})
}
override fun onResume() {
super.onResume()
activity?.title = getString(R.string.bookmark_select_folder_fragment_label)
(activity as? AppCompatActivity)?.supportActionBar?.show()
folderGuid = SelectBookmarkFolderFragmentArgs.fromBundle(arguments!!).folderGuid ?: BookmarkRoot.Root.id
checkIfSignedIn()
val accountManager = requireComponents.backgroundServices.accountManager
sharedViewModel.observeAccountManager(accountManager, owner = this)
lifecycleScope.launch(Main) {
bookmarkNode = withContext(IO) {
@ -96,13 +77,6 @@ class SelectBookmarkFolderFragment : Fragment(), AccountObserver {
}
}
private fun checkIfSignedIn() {
val accountManager = requireComponents.backgroundServices.accountManager
accountManager.register(this, owner = this)
accountManager.authenticatedAccount()?.let { bookmarkInteractor.onSignedIn() }
?: bookmarkInteractor.onSignedOut()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
val args: SelectBookmarkFolderFragmentArgs by navArgs()
if (!args.visitedAddBookmark) {
@ -125,11 +99,4 @@ class SelectBookmarkFolderFragment : Fragment(), AccountObserver {
else -> super.onOptionsItemSelected(item)
}
}
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
bookmarkInteractor.onSignedIn()
}
override fun onLoggedOut() {
bookmarkInteractor.onSignedOut()
}
}

View File

@ -1,30 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.library.bookmarks.selectfolder
import android.content.Context
import androidx.navigation.NavController
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel
import org.mozilla.fenix.library.bookmarks.SignInInteractor
class SelectBookmarkFolderInteractor(
private val context: Context,
private val navController: NavController,
private val sharedViewModel: BookmarksSharedViewModel
) : SignInInteractor {
override fun onSignInPressed() {
context.components.services.launchPairingSignIn(context, navController)
}
override fun onSignedIn() {
sharedViewModel.signedIn.postValue(true)
}
override fun onSignedOut() {
sharedViewModel.signedIn.postValue(false)
}
}

View File

@ -217,31 +217,4 @@ class BookmarkFragmentInteractorTest {
bookmarkController.handleBackPressed()
}
}
@Test
fun `clicked sign in on bookmarks screen`() {
interactor.onSignInPressed()
verify {
bookmarkController.handleSigningIn()
}
}
@Test
fun `got signed in signal on bookmarks screen`() {
interactor.onSignedIn()
verify {
sharedViewModel.signedIn.postValue(true)
}
}
@Test
fun `got signed out signal on bookmarks screen`() {
interactor.onSignedOut()
verify {
sharedViewModel.signedIn.postValue(false)
}
}
}

View File

@ -0,0 +1,63 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.library.bookmarks
import androidx.lifecycle.LifecycleOwner
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ObsoleteCoroutinesApi
import mozilla.components.concept.sync.AuthType
import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.support.test.mock
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.TestApplication
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@ObsoleteCoroutinesApi
@RunWith(RobolectricTestRunner::class)
@Config(application = TestApplication::class)
internal class BookmarksSharedViewModelTest {
private lateinit var viewModel: BookmarksSharedViewModel
@Before
fun setup() {
viewModel = BookmarksSharedViewModel()
viewModel.signedIn.observeForever { }
}
@Test
fun `onAuthenticated and onLoggedOut modify signedIn value`() {
viewModel.onLoggedOut()
assertFalse(viewModel.signedIn.value!!)
viewModel.onAuthenticated(mock(), AuthType.Existing)
assertTrue(viewModel.signedIn.value!!)
}
@Test
fun `observeAccountManager registers observer`() {
val accountManager: FxaAccountManager = mockk(relaxed = true)
val lifecycleOwner: LifecycleOwner = mockk()
every { accountManager.authenticatedAccount() } returns null
viewModel.observeAccountManager(accountManager, lifecycleOwner)
verify { accountManager.register(viewModel, lifecycleOwner) }
assertFalse(viewModel.signedIn.value!!)
every { accountManager.authenticatedAccount() } returns mockk()
viewModel.observeAccountManager(accountManager, lifecycleOwner)
verify { accountManager.register(viewModel, lifecycleOwner) }
assertTrue(viewModel.signedIn.value!!)
}
}