For #7272: Show info when saved logins list is empty. (#7891)

* For #7272: Show info when saved logins list is empty

* For #7272: Updated unit tests
fennec/nightly
Mihai Adrian 4 years ago committed by liuche
parent 10bf49918f
commit 622fdadde8

@ -23,5 +23,6 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) {
FromExceptions(R.id.exceptionsFragment), FromExceptions(R.id.exceptionsFragment),
FromAbout(R.id.aboutFragment), FromAbout(R.id.aboutFragment),
FromTrackingProtection(R.id.trackingProtectionFragment), FromTrackingProtection(R.id.trackingProtectionFragment),
FromDefaultBrowserSettingsFragment(R.id.defaultBrowserSettingsFragment) FromDefaultBrowserSettingsFragment(R.id.defaultBrowserSettingsFragment),
FromSavedLoginsFragment(R.id.savedLoginsFragment)
} }

@ -61,6 +61,7 @@ import org.mozilla.fenix.settings.DefaultBrowserSettingsFragmentDirections
import org.mozilla.fenix.settings.SettingsFragmentDirections import org.mozilla.fenix.settings.SettingsFragmentDirections
import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections
import org.mozilla.fenix.settings.about.AboutFragmentDirections import org.mozilla.fenix.settings.about.AboutFragmentDirections
import org.mozilla.fenix.settings.logins.SavedLoginsFragmentDirections
import org.mozilla.fenix.theme.DefaultThemeManager import org.mozilla.fenix.theme.DefaultThemeManager
import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.utils.BrowsersCache import org.mozilla.fenix.utils.BrowsersCache
@ -308,6 +309,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
DefaultBrowserSettingsFragmentDirections.actionDefaultBrowserSettingsFragmentToBrowserFragment( DefaultBrowserSettingsFragmentDirections.actionDefaultBrowserSettingsFragmentToBrowserFragment(
customTabSessionId customTabSessionId
) )
BrowserDirection.FromSavedLoginsFragment ->
SavedLoginsFragmentDirections.actionSavedLoginsFragmentToBrowserFragment(
customTabSessionId
)
} }
private fun load( private fun load(

@ -34,7 +34,8 @@ object SupportUtils {
SET_AS_DEFAULT_BROWSER("set-firefox-preview-default"), SET_AS_DEFAULT_BROWSER("set-firefox-preview-default"),
SEARCH_SUGGESTION("how-search-firefox-preview"), SEARCH_SUGGESTION("how-search-firefox-preview"),
CUSTOM_SEARCH_ENGINES("custom-search-engines"), CUSTOM_SEARCH_ENGINES("custom-search-engines"),
UPGRADE_FAQ("firefox-preview-upgrade-faqs") UPGRADE_FAQ("firefox-preview-upgrade-faqs"),
SYNC_SETUP("how-set-firefox-sync-firefox-preview")
} }
/** /**

@ -20,11 +20,14 @@ import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.lib.state.ext.consumeFrom
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.settings.SupportUtils
class SavedLoginsFragment : Fragment() { class SavedLoginsFragment : Fragment() {
private lateinit var savedLoginsStore: SavedLoginsFragmentStore private lateinit var savedLoginsStore: SavedLoginsFragmentStore
@ -53,7 +56,7 @@ class SavedLoginsFragment : Fragment() {
) )
) )
} }
savedLoginsInteractor = SavedLoginsInteractor(::itemClicked) savedLoginsInteractor = SavedLoginsInteractor(::itemClicked, ::openLearnMore)
savedLoginsView = SavedLoginsView(view.savedLoginsLayout, savedLoginsInteractor) savedLoginsView = SavedLoginsView(view.savedLoginsLayout, savedLoginsInteractor)
lifecycleScope.launch(Main) { loadAndMapLogins() } lifecycleScope.launch(Main) { loadAndMapLogins() }
return view return view
@ -86,6 +89,15 @@ class SavedLoginsFragment : Fragment() {
findNavController().navigate(directions) findNavController().navigate(directions)
} }
private fun openLearnMore() {
(activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
(SupportUtils.SumoTopic.SYNC_SETUP),
newTab = true,
from = BrowserDirection.FromSavedLoginsFragment
)
}
private suspend fun loadAndMapLogins() { private suspend fun loadAndMapLogins() {
val syncedLogins = withContext(IO) { val syncedLogins = withContext(IO) {
requireContext().components.core.syncablePasswordsStorage.withUnlocked { requireContext().components.core.syncablePasswordsStorage.withUnlocked {

@ -9,9 +9,13 @@ package org.mozilla.fenix.settings.logins
* Provides implementations for the SavedLoginsViewInteractor * Provides implementations for the SavedLoginsViewInteractor
*/ */
class SavedLoginsInteractor( class SavedLoginsInteractor(
private val itemClicked: (SavedLoginsItem) -> Unit private val itemClicked: (SavedLoginsItem) -> Unit,
private val learnMore: () -> Unit
) : SavedLoginsViewInteractor { ) : SavedLoginsViewInteractor {
override fun itemClicked(item: SavedLoginsItem) { override fun itemClicked(item: SavedLoginsItem) {
itemClicked.invoke(item) itemClicked.invoke(item)
} }
override fun onLearnMore() {
learnMore.invoke()
}
} }

@ -4,6 +4,9 @@
package org.mozilla.fenix.settings.logins package org.mozilla.fenix.settings.logins
import android.text.SpannableString
import android.text.method.LinkMovementMethod
import android.text.style.UnderlineSpan
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
@ -22,6 +25,8 @@ interface SavedLoginsViewInteractor {
* Called whenever one item is clicked * Called whenever one item is clicked
*/ */
fun itemClicked(item: SavedLoginsItem) fun itemClicked(item: SavedLoginsItem)
fun onLearnMore()
} }
/** /**
@ -43,10 +48,21 @@ class SavedLoginsView(
adapter = loginsAdapter adapter = loginsAdapter
layoutManager = LinearLayoutManager(containerView.context) layoutManager = LinearLayoutManager(containerView.context)
} }
val learnMoreText = view.saved_passwords_empty_learn_more.text.toString()
val textWithLink = SpannableString(learnMoreText).apply {
setSpan(UnderlineSpan(), 0, learnMoreText.length, 0)
}
with(view.saved_passwords_empty_learn_more) {
movementMethod = LinkMovementMethod.getInstance()
text = textWithLink
setOnClickListener { interactor.onLearnMore() }
}
} }
fun update(state: SavedLoginsFragmentState) { fun update(state: SavedLoginsFragmentState) {
view.saved_logins_list.isVisible = state.items.isNotEmpty() view.saved_logins_list.isVisible = state.items.isNotEmpty()
view.saved_passwords_empty_view.isVisible = state.items.isEmpty()
loginsAdapter.submitList(state.items) loginsAdapter.submitList(state.items)
} }
} }

@ -3,14 +3,44 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/saved_logins_wrapper" android:id="@+id/saved_logins_wrapper"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/saved_passwords_empty_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_margin="@dimen/exceptions_description_margin">
<TextView
android:id="@+id/saved_passwords_empty_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="4dp"
android:text="@string/preferences_passwords_saved_logins_description_empty_text"
android:textColor="?secondaryText"
android:textSize="16sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:id="@+id/saved_passwords_empty_learn_more"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="4dp"
android:text="@string/preferences_passwords_saved_logins_description_empty_learn_more_link"
android:textColor="?secondaryText"
android:textSize="16sp"
app:layout_constraintTop_toBottomOf="@id/saved_passwords_empty_message" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/saved_logins_list" android:id="@+id/saved_logins_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:visibility="gone" android:visibility="gone"
tools:listitem="@layout/logins_item" /> tools:listitem="@layout/logins_item" />
</FrameLayout> </FrameLayout>

@ -662,6 +662,11 @@
<action <action
android:id="@+id/action_savedLoginsFragment_to_savedLoginSiteInfoFragment" android:id="@+id/action_savedLoginsFragment_to_savedLoginSiteInfoFragment"
app:destination="@id/savedLoginSiteInfoFragment" /> app:destination="@id/savedLoginSiteInfoFragment" />
<action
android:id="@+id/action_savedLoginsFragment_to_browserFragment"
app:destination="@id/browserFragment"
app:popUpTo="@id/settingsFragment"
app:popUpToInclusive="true" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/savedLoginSiteInfoFragment" android:id="@+id/savedLoginSiteInfoFragment"

@ -1038,6 +1038,10 @@
<string name="preferences_passwords_sync_logins_sign_in">Sign in to Sync</string> <string name="preferences_passwords_sync_logins_sign_in">Sign in to Sync</string>
<!-- Preference to access list of saved logins --> <!-- Preference to access list of saved logins -->
<string name="preferences_passwords_saved_logins">Saved logins</string> <string name="preferences_passwords_saved_logins">Saved logins</string>
<!-- Description of empty list of saved passwords -->
<string name="preferences_passwords_saved_logins_description_empty_text">The logins you save or sync to Firefox will show up here.</string>
<!-- Preference to access list of saved logins -->
<string name="preferences_passwords_saved_logins_description_empty_learn_more_link">Learn more about Sync.</string>
<!-- Preference to access list of login exceptions that we never save logins for --> <!-- Preference to access list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions">Exceptions</string> <string name="preferences_passwords_exceptions">Exceptions</string>
<!-- Empty description of list of login exceptions that we never save logins for --> <!-- Empty description of list of login exceptions that we never save logins for -->

@ -13,8 +13,10 @@ class SavedLoginsInteractorTest {
@Test @Test
fun itemClicked() { fun itemClicked() {
val savedLoginClicked: (SavedLoginsItem) -> Unit = mockk(relaxed = true) val savedLoginClicked: (SavedLoginsItem) -> Unit = mockk(relaxed = true)
val learnMore: () -> Unit = mockk(relaxed = true)
val interactor = SavedLoginsInteractor( val interactor = SavedLoginsInteractor(
savedLoginClicked savedLoginClicked,
learnMore
) )
val item = SavedLoginsItem("mozilla.org", "username", "password") val item = SavedLoginsItem("mozilla.org", "username", "password")

Loading…
Cancel
Save