Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
The Tor Project
Applications
android-components
Commits
8e0b3010
Commit
8e0b3010
authored
Aug 25, 2020
by
ekager
Browse files
For #7134 - Adds login selection to prompt feature
parent
0ecf302c
Changes
6
Hide whitespace changes
Inline
Side-by-side
components/browser/engine-gecko-nightly/src/main/java/mozilla/components/browser/engine/gecko/autofill/GeckoLoginDelegateWrapper.kt
View file @
8e0b3010
...
...
@@ -9,8 +9,8 @@ import kotlinx.coroutines.GlobalScope
import
kotlinx.coroutines.launch
import
mozilla.components.concept.storage.Login
import
mozilla.components.concept.storage.LoginStorageDelegate
import
org.mozilla.geckoview.GeckoResult
import
org.mozilla.geckoview.Autocomplete
import
org.mozilla.geckoview.GeckoResult
/**
* This class exists only to convert incoming [LoginEntry] arguments into [Login]s, then forward
...
...
components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt
View file @
8e0b3010
...
...
@@ -13,9 +13,7 @@ import androidx.fragment.app.FragmentManager
import
kotlinx.coroutines.CoroutineScope
import
kotlinx.coroutines.cancel
import
kotlinx.coroutines.flow.collect
import
kotlinx.coroutines.flow.filter
import
kotlinx.coroutines.flow.map
import
kotlinx.coroutines.flow.mapNotNull
import
mozilla.components.browser.state.action.ContentAction
import
mozilla.components.browser.state.selector.findTabOrCustomTab
import
mozilla.components.browser.state.selector.findTabOrCustomTabOrSelectedTab
...
...
@@ -69,7 +67,6 @@ import mozilla.components.support.base.feature.OnNeedToRequestPermissions
import
mozilla.components.support.base.feature.PermissionsFeature
import
mozilla.components.support.base.log.logger.Logger
import
mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import
mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import
java.lang.ref.WeakReference
import
java.security.InvalidParameterException
import
java.util.Date
...
...
@@ -77,8 +74,6 @@ import java.util.Date
@VisibleForTesting
(
otherwise
=
PRIVATE
)
internal
const
val
FRAGMENT_TAG
=
"mozac_feature_prompt_dialog"
private
const
val
PROGRESS_ALMOST_COMPLETE
=
90
/**
* Feature for displaying native dialogs for html elements like: input type
* date, file, time, color, option, menu, authentication, confirmation and alerts.
...
...
@@ -132,7 +127,6 @@ class PromptFeature private constructor(
// These three scopes have identical lifetimes. We do not yet have a way of combining scopes
private
var
handlePromptScope
:
CoroutineScope
?
=
null
private
var
dismissPromptScope
:
CoroutineScope
?
=
null
private
var
sessionPromptScope
:
CoroutineScope
?
=
null
private
var
activePromptRequest
:
PromptRequest
?
=
null
internal
val
promptAbuserDetector
=
PromptAbuserDetector
()
...
...
@@ -243,6 +237,9 @@ class PromptFeature private constructor(
.
collect
{
state
->
state
?.
content
?.
let
{
if
(
it
.
promptRequest
!=
activePromptRequest
)
{
if
(
activePromptRequest
is
SelectLoginPrompt
)
{
loginPicker
?.
dismissCurrentLoginSelect
(
activePromptRequest
as
SelectLoginPrompt
)
}
onPromptRequested
(
state
)
}
else
if
(!
it
.
loading
)
{
promptAbuserDetector
.
resetJSAlertAbuseState
()
...
...
@@ -252,33 +249,23 @@ class PromptFeature private constructor(
}
}
// Dismiss all prompts when page loads are nearly finished. This prevents prompts from the
// previous page from covering content. See Fenix#5326
// Dismiss all prompts when page URL or session id changes. See Fenix#5326
dismissPromptScope
=
store
.
flowScoped
{
flow
->
flow
.
mapNotNull
{
state
->
state
.
findTabOrCustomTabOrSelectedTab
(
customTabId
)
}
.
ifChanged
{
it
.
content
.
progress
}
.
filter
{
it
.
content
.
progress
>=
PROGRESS_ALMOST_COMPLETE
}
.
collect
{
val
prompt
=
activePrompt
?.
get
()
if
(
prompt
?.
shouldDismissOnLoad
()
==
true
)
{
prompt
.
dismiss
()
}
activePrompt
?.
clear
()
loginPicker
?.
dismissCurrentLoginSelect
()
flow
.
ifAnyChanged
{
state
->
arrayOf
(
state
.
selectedTabId
,
state
.
findTabOrCustomTabOrSelectedTab
(
customTabId
)
?.
content
?.
url
)
}.
collect
{
if
(
activePromptRequest
is
SelectLoginPrompt
)
{
loginPicker
?.
dismissCurrentLoginSelect
(
activePromptRequest
as
SelectLoginPrompt
)
}
}
// Dismiss prompts when a new tab is selected.
sessionPromptScope
=
store
.
flowScoped
{
flow
->
flow
.
ifChanged
{
browserState
->
browserState
.
selectedTabId
}
.
collect
{
val
prompt
=
activePrompt
?.
get
()
if
(
prompt
?.
shouldDismissOnLoad
()
==
true
)
{
prompt
.
dismiss
()
}
activePrompt
?.
clear
()
loginPicker
?.
dismissCurrentLoginSelect
()
val
prompt
=
activePrompt
?.
get
()
if
(
prompt
?.
shouldDismissOnLoad
()
==
true
)
{
prompt
.
dismiss
()
}
activePrompt
?.
clear
()
}
}
fragmentManager
.
findFragmentByTag
(
FRAGMENT_TAG
)
?.
let
{
fragment
->
...
...
@@ -295,7 +282,6 @@ class PromptFeature private constructor(
override
fun
stop
()
{
handlePromptScope
?.
cancel
()
dismissPromptScope
?.
cancel
()
sessionPromptScope
?.
cancel
()
}
/**
...
...
components/feature/prompts/src/main/java/mozilla/components/feature/prompts/login/LoginPicker.kt
View file @
8e0b3010
...
...
@@ -4,11 +4,11 @@
package
mozilla.components.feature.prompts.login
import
androidx.annotation.VisibleForTesting
import
mozilla.components.browser.state.store.BrowserStore
import
mozilla.components.concept.engine.prompt.PromptRequest
import
mozilla.components.concept.storage.Login
import
mozilla.components.feature.prompts.consumePromptFrom
import
mozilla.components.support.base.log.logger.Logger
/**
* The [LoginPicker] displays a list of possible logins in a [LoginPickerView] for a site after
...
...
@@ -34,21 +34,13 @@ internal class LoginPicker(
}
internal
fun
handleSelectLoginRequest
(
request
:
PromptRequest
.
SelectLoginPrompt
)
{
if
(
currentRequest
!=
null
)
{
store
.
consumePromptFrom
(
sessionId
)
{
(
it
as
PromptRequest
.
SelectLoginPrompt
).
onDismiss
()
}
}
currentRequest
=
request
loginSelectBar
.
showPicker
(
request
.
logins
)
}
@VisibleForTesting
internal
var
currentRequest
:
PromptRequest
.
SelectLoginPrompt
?
=
null
override
fun
onLoginSelected
(
login
:
Login
)
{
(
currentRequest
as
PromptRequest
.
SelectLoginPrompt
).
onConfirm
(
login
)
currentRequest
=
null
store
.
consumePromptFrom
(
sessionId
)
{
if
(
it
is
PromptRequest
.
SelectLoginPrompt
)
it
.
onConfirm
(
login
)
}
loginSelectBar
.
hidePicker
()
}
...
...
@@ -57,13 +49,15 @@ internal class LoginPicker(
dismissCurrentLoginSelect
()
}
fun
dismissCurrentLoginSelect
()
{
if
(
currentRequest
!=
null
)
{
store
.
consumePromptFrom
(
sessionId
)
{
(
it
as
PromptRequest
.
SelectLoginPrompt
).
onDismiss
()
@Suppress
(
"TooGenericExceptionCaught"
)
fun
dismissCurrentLoginSelect
(
promptRequest
:
PromptRequest
.
SelectLoginPrompt
?
=
null
)
{
try
{
promptRequest
?.
let
{
it
.
onDismiss
()
}
?:
store
.
consumePromptFrom
(
sessionId
)
{
if
(
it
is
PromptRequest
.
SelectLoginPrompt
)
it
.
onDismiss
()
}
}
catch
(
e
:
RuntimeException
)
{
Logger
.
error
(
"Can't dismiss this login select prompt"
,
e
)
}
currentRequest
=
null
loginSelectBar
.
hidePicker
()
}
}
components/feature/prompts/src/main/res/layout/login_selection_list_item.xml
View file @
8e0b3010
...
...
@@ -5,9 +5,12 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:app=
"http://schemas.android.com/apk/res-auto"
xmlns:tools=
"http://schemas.android.com/tools"
android:id=
"@+id/login_item"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:background=
"?android:attr/selectableItemBackground"
android:clickable=
"true"
android:focusable=
"true"
android:minHeight=
"?android:attr/listPreferredItemHeight"
android:paddingStart=
"63dp"
android:paddingTop=
"8dp"
...
...
@@ -18,7 +21,11 @@
android:id=
"@+id/username"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:clickable=
"false"
android:focusable=
"false"
android:importantForAutofill=
"no"
android:textAppearance=
"?android:attr/textAppearanceListItem"
android:textIsSelectable=
"false"
app:layout_constraintEnd_toEndOf=
"parent"
app:layout_constraintStart_toStartOf=
"parent"
app:layout_constraintTop_toTopOf=
"parent"
...
...
@@ -28,12 +35,16 @@
android:id=
"@+id/password"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:clickable=
"false"
android:focusable=
"false"
android:importantForAutofill=
"no"
android:inputType=
"textPassword"
android:textAppearance=
"?android:attr/textAppearanceListItemSecondary"
android:textIsSelectable=
"false"
app:layout_constraintBottom_toBottomOf=
"parent"
app:layout_constraintEnd_toEndOf=
"parent"
app:layout_constraintStart_toStartOf=
"parent"
app:layout_constraintTop_toBottomOf=
"@id/username"
tools:text=
"password"
tools:ignore=
"TextViewEdits"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
tools:ignore=
"TextViewEdits"
tools:text=
"password"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
components/feature/prompts/src/main/res/layout/mozac_feature_login_multiselect_view.xml
View file @
8e0b3010
...
...
@@ -9,66 +9,78 @@
android:layout_height=
"wrap_content"
tools:parentTag=
"androidx.constraintlayout.widget.ConstraintLayout"
>
<androidx.appcompat.widget.AppCompatTextView
android:id=
"@+id/saved_logins_header"
android:layout_width=
"0dp"
android:layout_height=
"48dp"
android:background=
"?android:selectableItemBackground"
android:contentDescription=
"@string/mozac_feature_prompts_expand_logins_content_description"
android:drawablePadding=
"24dp"
android:gravity=
"center_vertical"
android:paddingStart=
"16dp"
android:paddingEnd=
"56dp"
android:text=
"@string/mozac_feature_prompts_saved_logins"
android:textColor=
"?android:colorEdgeEffect"
android:textSize=
"16sp"
app:drawableStartCompat=
"@drawable/mozac_ic_login"
app:drawableTint=
"?android:colorEdgeEffect"
app:layout_constraintEnd_toEndOf=
"parent"
app:layout_constraintStart_toStartOf=
"parent"
app:layout_constraintTop_toTopOf=
"parent"
/>
<ScrollView
android:id=
"@+id/login_scroll_view"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
>
<androidx.appcompat.widget.AppCompatImageView
android:id=
"@+id/mozac_feature_login_multiselect_expand"
android:layout_width=
"48dp"
android:layout_height=
"48dp"
android:layout_marginStart=
"8dp"
android:layout_marginEnd=
"8dp"
android:clickable=
"false"
android:focusable=
"false"
android:importantForAccessibility=
"no"
android:padding=
"16dp"
app:layout_constraintEnd_toEndOf=
"parent"
app:layout_constraintTop_toTopOf=
"parent"
app:srcCompat=
"@drawable/mozac_ic_arrowhead_down"
app:tint=
"?android:colorEdgeEffect"
/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id=
"@+id/scroll_child"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
>
<androidx.recyclerview.widget.RecyclerView
android:id=
"@+id/logins_list"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:orientation=
"horizontal"
android:visibility=
"gone"
app:layoutManager=
"androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintTop_toBottomOf=
"@id/mozac_feature_login_multiselect_expand"
tools:listitem=
"@layout/login_selection_list_item"
/>
<androidx.appcompat.widget.AppCompatTextView
android:id=
"@+id/saved_logins_header"
android:layout_width=
"0dp"
android:layout_height=
"48dp"
android:background=
"?android:selectableItemBackground"
android:contentDescription=
"@string/mozac_feature_prompts_expand_logins_content_description"
android:drawablePadding=
"24dp"
android:gravity=
"center_vertical"
android:paddingStart=
"16dp"
android:paddingEnd=
"56dp"
android:text=
"@string/mozac_feature_prompts_saved_logins"
android:textColor=
"?android:colorEdgeEffect"
android:textSize=
"16sp"
app:drawableStartCompat=
"@drawable/mozac_ic_login"
app:drawableTint=
"?android:colorEdgeEffect"
app:layout_constraintEnd_toEndOf=
"parent"
app:layout_constraintStart_toStartOf=
"parent"
app:layout_constraintTop_toTopOf=
"parent"
/>
<androidx.appcompat.widget.AppCompatImageView
android:id=
"@+id/mozac_feature_login_multiselect_expand"
android:layout_width=
"48dp"
android:layout_height=
"48dp"
android:layout_marginStart=
"8dp"
android:layout_marginEnd=
"8dp"
android:clickable=
"false"
android:focusable=
"false"
android:importantForAccessibility=
"no"
android:padding=
"16dp"
app:layout_constraintEnd_toEndOf=
"parent"
app:layout_constraintTop_toTopOf=
"parent"
app:srcCompat=
"@drawable/mozac_ic_arrowhead_down"
app:tint=
"?android:colorEdgeEffect"
/>
<androidx.recyclerview.widget.RecyclerView
android:id=
"@+id/logins_list"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:orientation=
"horizontal"
android:visibility=
"gone"
app:layoutManager=
"androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintTop_toBottomOf=
"@id/mozac_feature_login_multiselect_expand"
tools:listitem=
"@layout/login_selection_list_item"
/>
<androidx.appcompat.widget.AppCompatTextView
android:id=
"@+id/manage_logins"
android:layout_width=
"0dp"
android:layout_height=
"48dp"
android:background=
"?android:selectableItemBackground"
android:drawablePadding=
"24dp"
android:gravity=
"center_vertical"
android:paddingStart=
"16dp"
android:paddingEnd=
"0dp"
android:text=
"@string/mozac_feature_prompts_manage_logins"
android:textColor=
"?android:textColorPrimary"
android:textSize=
"16sp"
android:visibility=
"gone"
app:drawableStartCompat=
"@drawable/mozac_ic_settings"
app:drawableTint=
"?android:textColorPrimary"
app:layout_constraintEnd_toEndOf=
"parent"
app:layout_constraintStart_toStartOf=
"parent"
app:layout_constraintTop_toBottomOf=
"@id/logins_list"
/>
<androidx.appcompat.widget.AppCompatTextView
android:id=
"@+id/manage_logins"
android:layout_width=
"0dp"
android:layout_height=
"48dp"
android:background=
"?android:selectableItemBackground"
android:drawablePadding=
"24dp"
android:gravity=
"center_vertical"
android:paddingStart=
"16dp"
android:paddingEnd=
"0dp"
android:text=
"@string/mozac_feature_prompts_manage_logins"
android:textColor=
"?android:textColorPrimary"
android:textSize=
"16sp"
android:visibility=
"gone"
app:drawableStartCompat=
"@drawable/mozac_ic_settings"
app:drawableTint=
"?android:textColorPrimary"
app:layout_constraintEnd_toEndOf=
"parent"
app:layout_constraintStart_toStartOf=
"parent"
app:layout_constraintTop_toBottomOf=
"@id/logins_list"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</merge>
components/feature/prompts/src/test/java/mozilla/components/feature/prompts/PromptFeatureTest.kt
View file @
8e0b3010
...
...
@@ -23,6 +23,7 @@ import kotlinx.coroutines.test.setMain
import
mozilla.components.browser.state.action.ContentAction
import
mozilla.components.browser.state.action.TabListAction
import
mozilla.components.browser.state.state.BrowserState
import
mozilla.components.browser.state.state.ContentState
import
mozilla.components.browser.state.state.TabSessionState
import
mozilla.components.browser.state.state.createCustomTab
import
mozilla.components.browser.state.state.createTab
...
...
@@ -1025,7 +1026,7 @@ class PromptFeatureTest {
}
@Test
fun
`dialog
will
be
dismissed
if
progress
reaches
90
%
`
()
{
fun
`dialog
will
be
dismissed
if
tab
ID
changes
`
()
{
val
feature
=
spy
(
PromptFeature
(
activity
=
mock
(),
...
...
@@ -1047,15 +1048,15 @@ class PromptFeatureTest {
whenever
(
fragment
.
shouldDismissOnLoad
()).
thenReturn
(
true
)
feature
.
activePrompt
=
WeakReference
(
fragment
)
store
.
dispatch
(
ContentAction
.
UpdateProgressAction
(
tabId
,
0
)).
joinBlocking
()
store
.
dispatch
(
ContentAction
.
UpdateProgressAction
(
tabId
,
10
)).
joinBlocking
()
store
.
dispatch
(
ContentAction
.
UpdateProgressAction
(
tabId
,
11
)).
joinBlocking
()
store
.
dispatch
(
ContentAction
.
UpdateProgressAct
ion
(
ta
bId
,
28
)).
joinBlocking
()
store
.
dispatch
(
ContentAction
.
UpdateProgressAction
(
tabId
,
32
)).
joinBlocking
()
store
.
dispatch
(
ContentAction
.
UpdateProgressAction
(
tabId
,
49
)).
joinBlocking
(
)
store
.
dispatch
(
ContentAction
.
UpdateProgressAction
(
tabId
,
60
)).
joinBlocking
()
store
.
dispatch
(
ContentAction
.
UpdateProgressAction
(
tabId
,
89
)).
joinBlocking
(
)
store
.
dispatch
(
ContentAction
.
UpdateProgressAction
(
tabId
,
90
)
).
joinBlocking
()
val
secondTabId
=
"second-test-tab"
store
.
dispatch
(
TabListAction
.
AddTabAction
(
TabSess
ion
S
ta
te
(
id
=
secondTabId
,
content
=
ContentState
(
url
=
"mozilla.org"
)
),
select
=
true
)
).
joinBlocking
()
verify
(
fragment
,
times
(
1
)).
dismiss
()
}
...
...
@@ -1091,7 +1092,7 @@ class PromptFeatureTest {
}
@Test
fun
`dialog
will
be
dismissed
if
new
page
load
progress
skips
past
90
%
`
()
{
fun
`dialog
will
be
dismissed
if
tab
URL
changes
`
()
{
val
feature
=
spy
(
PromptFeature
(
activity
=
mock
(),
...
...
@@ -1113,10 +1114,7 @@ class PromptFeatureTest {
whenever
(
fragment
.
shouldDismissOnLoad
()).
thenReturn
(
true
)
feature
.
activePrompt
=
WeakReference
(
fragment
)
store
.
dispatch
(
ContentAction
.
UpdateProgressAction
(
tabId
,
0
)).
joinBlocking
()
store
.
dispatch
(
ContentAction
.
UpdateProgressAction
(
tabId
,
10
)).
joinBlocking
()
store
.
dispatch
(
ContentAction
.
UpdateProgressAction
(
tabId
,
100
)).
joinBlocking
()
store
.
dispatch
(
ContentAction
.
UpdateUrlAction
(
tabId
,
"mozilla.org"
)).
joinBlocking
()
verify
(
fragment
,
times
(
1
)).
dismiss
()
}
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment