For #21897 - Convert inactive tabs to compose
parent
0cbf4d9b7f
commit
08a84f8353
@ -1,31 +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.tabstray.browser
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import mozilla.components.browser.tabstray.TabsTray
|
|
||||||
import mozilla.components.lib.state.helpers.AbstractBinding
|
|
||||||
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
|
|
||||||
import org.mozilla.fenix.tabstray.TabsTrayState
|
|
||||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An inactive tabs observer that updates the provided [TabsTray].
|
|
||||||
*/
|
|
||||||
class InactiveTabsBinding(
|
|
||||||
store: TabsTrayStore,
|
|
||||||
private val tray: TabsTray
|
|
||||||
) : AbstractBinding<TabsTrayState>(store) {
|
|
||||||
override suspend fun onState(flow: Flow<TabsTrayState>) {
|
|
||||||
flow.map { it.inactiveTabs }
|
|
||||||
.ifChanged()
|
|
||||||
.collect {
|
|
||||||
// We pass null for the selected tab id here, because inactive tabs doesn't care.
|
|
||||||
tray.updateTabs(it, null, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,278 @@
|
|||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
@file:Suppress("TooManyFunctions")
|
||||||
|
|
||||||
|
package org.mozilla.fenix.tabstray.inactivetabs
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Card
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.Font
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import mozilla.components.browser.state.state.ContentState
|
||||||
|
import mozilla.components.browser.state.state.TabSessionState
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.compose.PrimaryText
|
||||||
|
import org.mozilla.fenix.compose.SecondaryText
|
||||||
|
import org.mozilla.fenix.compose.button.TextButton
|
||||||
|
import org.mozilla.fenix.compose.list.ExpandableListHeader
|
||||||
|
import org.mozilla.fenix.compose.list.FaviconListItem
|
||||||
|
import org.mozilla.fenix.ext.toShortUrl
|
||||||
|
import org.mozilla.fenix.tabstray.ext.toDisplayTitle
|
||||||
|
import org.mozilla.fenix.theme.FirefoxTheme
|
||||||
|
import org.mozilla.fenix.theme.Theme
|
||||||
|
|
||||||
|
private val ROUNDED_CORNER_SHAPE = RoundedCornerShape(8.dp)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Top-level list for displaying an expandable section of Inactive Tabs.
|
||||||
|
*
|
||||||
|
* @param inactiveTabs List of [TabSessionState] to display.
|
||||||
|
* @param expanded Whether to show the inactive tabs section expanded or collapsed.
|
||||||
|
* @param showAutoCloseDialog Whether to show the auto close inactive tabs dialog.
|
||||||
|
* @param onHeaderClick Called when the user clicks on the inactive tabs section header.
|
||||||
|
* @param onDeleteAllButtonClick Called when the user clicks on the delete all inactive tabs button.
|
||||||
|
* @param onAutoCloseDismissClick Called when the user clicks on the auto close dialog's dismiss button.
|
||||||
|
* @param onEnableAutoCloseClick Called when the user clicks on the auto close dialog's enable button.
|
||||||
|
* @param onTabClick Called when the user clicks on a specific inactive tab.
|
||||||
|
* @param onTabCloseClick Called when the user clicks on a specific inactive tab's close button.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
@Suppress("LongParameterList")
|
||||||
|
fun InactiveTabsList(
|
||||||
|
inactiveTabs: List<TabSessionState>,
|
||||||
|
expanded: Boolean,
|
||||||
|
showAutoCloseDialog: Boolean,
|
||||||
|
onHeaderClick: () -> Unit,
|
||||||
|
onDeleteAllButtonClick: () -> Unit,
|
||||||
|
onAutoCloseDismissClick: () -> Unit,
|
||||||
|
onEnableAutoCloseClick: () -> Unit,
|
||||||
|
onTabClick: (TabSessionState) -> Unit,
|
||||||
|
onTabCloseClick: (TabSessionState) -> Unit,
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
shape = ROUNDED_CORNER_SHAPE,
|
||||||
|
backgroundColor = FirefoxTheme.colors.layer2,
|
||||||
|
border = BorderStroke(
|
||||||
|
width = 1.dp,
|
||||||
|
color = FirefoxTheme.colors.borderPrimary,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
InactiveTabsHeader(
|
||||||
|
expanded = expanded,
|
||||||
|
onClick = onHeaderClick,
|
||||||
|
onDeleteAllClick = onDeleteAllButtonClick,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (expanded) {
|
||||||
|
if (showAutoCloseDialog) {
|
||||||
|
InactiveTabsAutoClosePrompt(
|
||||||
|
onDismissClick = onAutoCloseDismissClick,
|
||||||
|
onEnableAutoCloseClick = onEnableAutoCloseClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
inactiveTabs.forEach { tab ->
|
||||||
|
val tabUrl = tab.content.url.toShortUrl()
|
||||||
|
|
||||||
|
FaviconListItem(
|
||||||
|
label = tab.toDisplayTitle(),
|
||||||
|
description = tabUrl,
|
||||||
|
onClick = { onTabClick(tab) },
|
||||||
|
url = tabUrl,
|
||||||
|
iconPainter = painterResource(R.drawable.mozac_ic_close),
|
||||||
|
iconDescription = stringResource(R.string.content_description_close_button),
|
||||||
|
onIconClick = { onTabCloseClick(tab) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collapsible header for the Inactive Tabs section.
|
||||||
|
*
|
||||||
|
* @param expanded Whether the section is expanded.
|
||||||
|
* @param onClick Called when the user clicks on the header.
|
||||||
|
* @param onDeleteAllClick Called when the user clicks on the delete all button.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun InactiveTabsHeader(
|
||||||
|
expanded: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onDeleteAllClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
ExpandableListHeader(
|
||||||
|
headerText = stringResource(R.string.inactive_tabs_title),
|
||||||
|
expanded = expanded,
|
||||||
|
expandActionContentDescription = stringResource(R.string.inactive_tabs_expand_content_description),
|
||||||
|
collapseActionContentDescription = stringResource(R.string.inactive_tabs_collapse_content_description),
|
||||||
|
onClick = onClick,
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = onDeleteAllClick,
|
||||||
|
modifier = Modifier.padding(horizontal = 4.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_delete),
|
||||||
|
contentDescription = stringResource(R.string.inactive_tabs_delete_all),
|
||||||
|
tint = FirefoxTheme.colors.iconPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inactive Tabs auto close dialog.
|
||||||
|
*
|
||||||
|
* @param onDismissClick Called when the user clicks on the auto close dialog's dismiss button.
|
||||||
|
* @param onEnableAutoCloseClick Called when the user clicks on the auto close dialog's enable button.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun InactiveTabsAutoClosePrompt(
|
||||||
|
onDismissClick: () -> Unit,
|
||||||
|
onEnableAutoCloseClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
shape = ROUNDED_CORNER_SHAPE,
|
||||||
|
backgroundColor = FirefoxTheme.colors.layer2,
|
||||||
|
border = BorderStroke(
|
||||||
|
width = 1.dp,
|
||||||
|
color = FirefoxTheme.colors.borderPrimary,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
horizontalAlignment = Alignment.End,
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
PrimaryText(
|
||||||
|
text = stringResource(R.string.tab_tray_inactive_auto_close_title),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontFamily = FontFamily(Font(R.font.metropolis_semibold)),
|
||||||
|
)
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = onDismissClick,
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.mozac_ic_close_20),
|
||||||
|
contentDescription =
|
||||||
|
stringResource(R.string.tab_tray_inactive_auto_close_button_content_description),
|
||||||
|
tint = FirefoxTheme.colors.iconPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SecondaryText(
|
||||||
|
text = stringResource(
|
||||||
|
R.string.tab_tray_inactive_auto_close_body_2,
|
||||||
|
stringResource(R.string.app_name)
|
||||||
|
),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
fontSize = 14.sp,
|
||||||
|
)
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
text = stringResource(R.string.tab_tray_inactive_turn_on_auto_close_button_2),
|
||||||
|
onClick = onEnableAutoCloseClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview(name = "Auto close dialog dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Preview(name = "Auto close dialog light", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||||
|
private fun InactiveTabsAutoClosePromptPreview() {
|
||||||
|
FirefoxTheme(theme = Theme.getTheme(isPrivate = false)) {
|
||||||
|
Box(Modifier.background(FirefoxTheme.colors.layer1)) {
|
||||||
|
InactiveTabsAutoClosePrompt(
|
||||||
|
onDismissClick = {},
|
||||||
|
onEnableAutoCloseClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview(name = "Full preview dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Preview(name = "Full preview light", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||||
|
private fun InactiveTabsListPreview() {
|
||||||
|
var expanded by remember { mutableStateOf(true) }
|
||||||
|
var showAutoClosePrompt by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
FirefoxTheme(theme = Theme.getTheme(isPrivate = false)) {
|
||||||
|
Box(Modifier.background(FirefoxTheme.colors.layer1)) {
|
||||||
|
InactiveTabsList(
|
||||||
|
inactiveTabs = generateFakeInactiveTabsList(),
|
||||||
|
expanded = expanded,
|
||||||
|
showAutoCloseDialog = showAutoClosePrompt,
|
||||||
|
onHeaderClick = { expanded = !expanded },
|
||||||
|
onDeleteAllButtonClick = {},
|
||||||
|
onAutoCloseDismissClick = { showAutoClosePrompt = !showAutoClosePrompt },
|
||||||
|
onEnableAutoCloseClick = { showAutoClosePrompt = !showAutoClosePrompt },
|
||||||
|
onTabClick = {},
|
||||||
|
onTabCloseClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateFakeInactiveTabsList(): List<TabSessionState> =
|
||||||
|
listOf(
|
||||||
|
TabSessionState(
|
||||||
|
id = "tabId",
|
||||||
|
content = ContentState(
|
||||||
|
url = "www.mozilla.com",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
TabSessionState(
|
||||||
|
id = "tabId",
|
||||||
|
content = ContentState(
|
||||||
|
url = "www.google.com",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- 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/. -->
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize="true">
|
|
||||||
<item android:drawable="@drawable/rounded_top_corners" android:state_activated="true" />
|
|
||||||
<item android:drawable="@drawable/rounded_all_corners" />
|
|
||||||
</selector>
|
|
@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- 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/. -->
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
<stroke
|
|
||||||
android:width="1dp"
|
|
||||||
android:color="?borderPrimary" />
|
|
||||||
|
|
||||||
<corners android:radius="8dp" />
|
|
||||||
|
|
||||||
<solid android:color="?attr/layer2" />
|
|
||||||
</shape>
|
|
@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- 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/. -->
|
|
||||||
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<solid android:color="?attr/layer2" />
|
|
||||||
<corners android:bottomLeftRadius="@dimen/tab_corner_radius" android:bottomRightRadius="@dimen/tab_corner_radius" />
|
|
||||||
</shape>
|
|
@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- 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/. -->
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<solid android:color="?attr/layer2" />
|
|
||||||
<corners android:topLeftRadius="@dimen/tab_corner_radius" android:topRightRadius="@dimen/tab_corner_radius" />
|
|
||||||
</shape>
|
|
@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!-- 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/. -->
|
|
||||||
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:background="@drawable/rounded_bottom_corners"
|
|
||||||
android:backgroundTint="?borderPrimary"
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
android:paddingStart="1dp"
|
|
||||||
android:paddingEnd="1dp"
|
|
||||||
android:paddingBottom="1dp">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@drawable/rounded_bottom_corners"
|
|
||||||
android:paddingBottom="8dp">
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</FrameLayout>
|
|
@ -1,70 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!-- 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/. -->
|
|
||||||
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@+id/inactive_header_border"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:background="@drawable/card_list_row_background"
|
|
||||||
android:backgroundTint="?borderPrimary"
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
android:paddingStart="1dp"
|
|
||||||
android:paddingTop="1dp"
|
|
||||||
android:paddingEnd="1dp">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:background="@drawable/card_list_row_background"
|
|
||||||
android:clickable="false"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:focusable="true"
|
|
||||||
android:foreground="?android:attr/selectableItemBackground"
|
|
||||||
android:paddingStart="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/inactive_title"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:gravity="start"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:minLines="1"
|
|
||||||
android:text="@string/inactive_tabs_title"
|
|
||||||
android:textAppearance="@style/Header16TextStyle"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:text="Inactive tabs" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/chevron"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:contentDescription="@string/tab_menu"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/inactive_title"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/inactive_title"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/inactive_title"
|
|
||||||
app:srcCompat="@drawable/ic_chevron" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/delete"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:paddingVertical="12dp"
|
|
||||||
android:contentDescription="@string/inactive_tabs_delete_all"
|
|
||||||
android:foreground="?android:attr/selectableItemBackground"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:srcCompat="@drawable/ic_delete" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</FrameLayout>
|
|
@ -1,22 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!-- 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/. -->
|
|
||||||
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:background="?borderPrimary"
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
android:paddingStart="1dp"
|
|
||||||
android:paddingEnd="1dp">
|
|
||||||
|
|
||||||
<mozilla.components.ui.widgets.WidgetSiteItemView
|
|
||||||
android:id="@+id/site_list_item"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/layer2"
|
|
||||||
android:foreground="?android:attr/selectableItemBackground"
|
|
||||||
android:minHeight="@dimen/mozac_widget_site_item_height" />
|
|
||||||
</FrameLayout>
|
|
@ -1,78 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- 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/. -->
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="16dp"
|
|
||||||
android:paddingHorizontal="1dp"
|
|
||||||
android:background="?borderPrimary">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/layer2"
|
|
||||||
android:clickable="false"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:focusable="true"
|
|
||||||
android:padding="12dp">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:id="@+id/banner_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:clickable="false"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:background="@drawable/inactive_tab_auto_close_border_background"
|
|
||||||
android:focusable="true"
|
|
||||||
android:padding="12dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/banner_info_message"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:text="@string/tab_tray_inactive_auto_close_title"
|
|
||||||
android:textAppearance="@style/Header14TextStyle"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/close_button"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageButton
|
|
||||||
android:id="@+id/close_button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/tab_tray_inactive_auto_close_button_content_description"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:srcCompat="@drawable/ic_close" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/message"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:text="@string/tab_tray_inactive_auto_close_body_2"
|
|
||||||
android:textAppearance="@style/Body14TextStyle"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/banner_info_message"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/action"
|
|
||||||
style="@style/DialogButtonStyleDark"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:layout_marginEnd="3dp"
|
|
||||||
android:textAllCaps="true"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
android:text="@string/tab_tray_inactive_turn_on_auto_close_button_2"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/message" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</FrameLayout>
|
|
||||||
</FrameLayout>
|
|
@ -1,39 +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.tabstray.browser
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.mockk
|
|
||||||
import io.mockk.verify
|
|
||||||
import mozilla.components.support.test.robolectric.testContext
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.mozilla.fenix.components.AppStore
|
|
||||||
import org.mozilla.fenix.ext.components
|
|
||||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
|
||||||
import org.mozilla.fenix.tabstray.TabsTrayInteractor
|
|
||||||
import org.mozilla.fenix.tabstray.browser.InactiveTabViewHolder.HeaderHolder
|
|
||||||
|
|
||||||
@RunWith(FenixRobolectricTestRunner::class)
|
|
||||||
class InactiveTabViewHolderTest {
|
|
||||||
@Test
|
|
||||||
fun `HeaderHolder - WHEN clicked THEN notify the interactor`() {
|
|
||||||
every { testContext.components.appStore } returns AppStore()
|
|
||||||
val view = LayoutInflater.from(testContext).inflate(HeaderHolder.LAYOUT_ID, null)
|
|
||||||
val interactor: InactiveTabsInteractor = mockk(relaxed = true)
|
|
||||||
val tabsTrayInteractor: TabsTrayInteractor = mockk(relaxed = true)
|
|
||||||
val viewHolder = HeaderHolder(view, interactor, tabsTrayInteractor)
|
|
||||||
|
|
||||||
val initialActivatedState = view.isActivated
|
|
||||||
|
|
||||||
viewHolder.itemView.performClick()
|
|
||||||
|
|
||||||
verify { interactor.onHeaderClicked(any()) }
|
|
||||||
|
|
||||||
assertEquals(!initialActivatedState, view.isActivated)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +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.tabstray.browser
|
|
||||||
|
|
||||||
import io.mockk.mockk
|
|
||||||
import io.mockk.verify
|
|
||||||
import mozilla.components.browser.state.state.createTab
|
|
||||||
import mozilla.components.browser.tabstray.TabsTray
|
|
||||||
import mozilla.components.support.test.ext.joinBlocking
|
|
||||||
import mozilla.components.support.test.rule.MainCoroutineRule
|
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.mozilla.fenix.tabstray.TabsTrayAction
|
|
||||||
import org.mozilla.fenix.tabstray.TabsTrayStore
|
|
||||||
|
|
||||||
class InactiveTabsBindingTest {
|
|
||||||
val store = TabsTrayStore()
|
|
||||||
val tray: TabsTray = mockk(relaxed = true)
|
|
||||||
val binding = InactiveTabsBinding(store, tray)
|
|
||||||
|
|
||||||
@get:Rule
|
|
||||||
val coroutinesTestRule = MainCoroutineRule()
|
|
||||||
|
|
||||||
@After
|
|
||||||
fun teardown() {
|
|
||||||
binding.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `WHEN the store is updated THEN notify the tabs tray`() {
|
|
||||||
assertTrue(store.state.inactiveTabs.isEmpty())
|
|
||||||
|
|
||||||
store.dispatch(TabsTrayAction.UpdateInactiveTabs(listOf(createTab("https://mozilla.org")))).joinBlocking()
|
|
||||||
|
|
||||||
binding.start()
|
|
||||||
|
|
||||||
assertTrue(store.state.inactiveTabs.isNotEmpty())
|
|
||||||
|
|
||||||
verify { tray.updateTabs(any(), any(), any()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `WHEN non-inactive tabs are updated THEN do not notify the tabs tray`() {
|
|
||||||
assertTrue(store.state.inactiveTabs.isEmpty())
|
|
||||||
|
|
||||||
store.dispatch(TabsTrayAction.UpdatePrivateTabs(listOf(createTab("https://mozilla.org")))).joinBlocking()
|
|
||||||
|
|
||||||
binding.start()
|
|
||||||
|
|
||||||
assertTrue(store.state.inactiveTabs.isEmpty())
|
|
||||||
|
|
||||||
verify { tray.updateTabs(emptyList(), null, null) }
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue