1
0
Fork 0

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
master
Mihai Adrian 2020-01-25 04:13:44 +02:00 committed by liuche
parent 10bf49918f
commit 622fdadde8
10 changed files with 85 additions and 5 deletions

View File

@ -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)
} }

View File

@ -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(

View File

@ -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")
} }
/** /**

View File

@ -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 {

View File

@ -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()
}
} }

View File

@ -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)
} }
} }

View File

@ -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>

View File

@ -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"

View File

@ -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 -->

View File

@ -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")