1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 @file:Suppress("TooManyFunctions")
7 package org.mozilla.fenix.tabstray.inactivetabs
9 import android.content.res.Configuration
10 import androidx.compose.foundation.BorderStroke
11 import androidx.compose.foundation.background
12 import androidx.compose.foundation.layout.Box
13 import androidx.compose.foundation.layout.Column
14 import androidx.compose.foundation.layout.Row
15 import androidx.compose.foundation.layout.Spacer
16 import androidx.compose.foundation.layout.fillMaxWidth
17 import androidx.compose.foundation.layout.height
18 import androidx.compose.foundation.layout.padding
19 import androidx.compose.foundation.layout.size
20 import androidx.compose.foundation.shape.RoundedCornerShape
21 import androidx.compose.material.Card
22 import androidx.compose.material.Icon
23 import androidx.compose.material.IconButton
24 import androidx.compose.material.Text
25 import androidx.compose.runtime.Composable
26 import androidx.compose.runtime.getValue
27 import androidx.compose.runtime.mutableStateOf
28 import androidx.compose.runtime.remember
29 import androidx.compose.runtime.setValue
30 import androidx.compose.ui.Alignment
31 import androidx.compose.ui.Modifier
32 import androidx.compose.ui.graphics.asImageBitmap
33 import androidx.compose.ui.graphics.painter.BitmapPainter
34 import androidx.compose.ui.res.painterResource
35 import androidx.compose.ui.res.stringResource
36 import androidx.compose.ui.tooling.preview.Preview
37 import androidx.compose.ui.unit.dp
38 import androidx.compose.ui.unit.sp
39 import mozilla.components.browser.state.state.ContentState
40 import mozilla.components.browser.state.state.TabSessionState
41 import org.mozilla.fenix.R
42 import org.mozilla.fenix.compose.button.TextButton
43 import org.mozilla.fenix.compose.list.ExpandableListHeader
44 import org.mozilla.fenix.compose.list.FaviconListItem
45 import org.mozilla.fenix.ext.toShortUrl
46 import org.mozilla.fenix.tabstray.ext.toDisplayTitle
47 import org.mozilla.fenix.theme.FirefoxTheme
49 private val ROUNDED_CORNER_SHAPE = RoundedCornerShape(8.dp)
52 * Top-level list for displaying an expandable section of Inactive Tabs.
54 * @param inactiveTabs List of [TabSessionState] to display.
55 * @param expanded Whether to show the inactive tabs section expanded or collapsed.
56 * @param showAutoCloseDialog Whether to show the auto close inactive tabs dialog.
57 * @param onHeaderClick Called when the user clicks on the inactive tabs section header.
58 * @param onDeleteAllButtonClick Called when the user clicks on the delete all inactive tabs button.
59 * @param onAutoCloseDismissClick Called when the user clicks on the auto close dialog's dismiss button.
60 * @param onEnableAutoCloseClick Called when the user clicks on the auto close dialog's enable button.
61 * @param onTabClick Called when the user clicks on a specific inactive tab.
62 * @param onTabCloseClick Called when the user clicks on a specific inactive tab's close button.
65 @Suppress("LongParameterList")
67 inactiveTabs: List<TabSessionState>,
69 showAutoCloseDialog: Boolean,
70 onHeaderClick: (Boolean) -> Unit,
71 onDeleteAllButtonClick: () -> Unit,
72 onAutoCloseDismissClick: () -> Unit,
73 onEnableAutoCloseClick: () -> Unit,
74 onTabClick: (TabSessionState) -> Unit,
75 onTabCloseClick: (TabSessionState) -> Unit,
78 modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
79 shape = ROUNDED_CORNER_SHAPE,
80 backgroundColor = FirefoxTheme.colors.layer2,
81 border = BorderStroke(
83 color = FirefoxTheme.colors.borderPrimary,
87 modifier = Modifier.fillMaxWidth(),
91 onClick = { onHeaderClick(!expanded) },
92 onDeleteAllClick = onDeleteAllButtonClick,
96 if (showAutoCloseDialog) {
97 InactiveTabsAutoClosePrompt(
98 onDismissClick = onAutoCloseDismissClick,
99 onEnableAutoCloseClick = onEnableAutoCloseClick,
104 inactiveTabs.forEach { tab ->
105 val tabUrl = tab.content.url.toShortUrl()
106 val faviconPainter = tab.content.icon?.run {
108 BitmapPainter(asImageBitmap())
112 label = tab.toDisplayTitle(),
113 description = tabUrl,
114 faviconPainter = faviconPainter,
115 onClick = { onTabClick(tab) },
117 iconPainter = painterResource(R.drawable.mozac_ic_cross_24),
118 iconDescription = stringResource(R.string.content_description_close_button),
119 onIconClick = { onTabCloseClick(tab) },
124 Spacer(modifier = Modifier.height(8.dp))
131 * Collapsible header for the Inactive Tabs section.
133 * @param expanded Whether the section is expanded.
134 * @param onClick Called when the user clicks on the header.
135 * @param onDeleteAllClick Called when the user clicks on the delete all button.
138 private fun InactiveTabsHeader(
141 onDeleteAllClick: () -> Unit,
143 ExpandableListHeader(
144 headerText = stringResource(R.string.inactive_tabs_title),
145 headerTextStyle = FirefoxTheme.typography.headline7,
147 expandActionContentDescription = stringResource(R.string.inactive_tabs_expand_content_description),
148 collapseActionContentDescription = stringResource(R.string.inactive_tabs_collapse_content_description),
152 onClick = onDeleteAllClick,
153 modifier = Modifier.padding(horizontal = 4.dp),
156 painter = painterResource(R.drawable.ic_delete),
157 contentDescription = stringResource(R.string.inactive_tabs_delete_all),
158 tint = FirefoxTheme.colors.iconPrimary,
165 * Inactive Tabs auto close dialog.
167 * @param onDismissClick Called when the user clicks on the auto close dialog's dismiss button.
168 * @param onEnableAutoCloseClick Called when the user clicks on the auto close dialog's enable button.
171 private fun InactiveTabsAutoClosePrompt(
172 onDismissClick: () -> Unit,
173 onEnableAutoCloseClick: () -> Unit,
176 modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
177 shape = ROUNDED_CORNER_SHAPE,
178 backgroundColor = FirefoxTheme.colors.layer2,
179 border = BorderStroke(
181 color = FirefoxTheme.colors.borderPrimary,
185 modifier = Modifier.padding(horizontal = 12.dp),
186 horizontalAlignment = Alignment.End,
188 Spacer(modifier = Modifier.height(12.dp))
191 verticalAlignment = Alignment.CenterVertically,
194 text = stringResource(R.string.tab_tray_inactive_auto_close_title),
195 color = FirefoxTheme.colors.textPrimary,
196 modifier = Modifier.weight(1f),
197 style = FirefoxTheme.typography.headline8,
201 onClick = onDismissClick,
202 modifier = Modifier.size(20.dp),
205 painter = painterResource(R.drawable.mozac_ic_close_20),
207 stringResource(R.string.tab_tray_inactive_auto_close_button_content_description),
208 tint = FirefoxTheme.colors.iconPrimary,
214 text = stringResource(
215 R.string.tab_tray_inactive_auto_close_body_2,
216 stringResource(R.string.app_name),
218 color = FirefoxTheme.colors.textSecondary,
219 modifier = Modifier.fillMaxWidth(),
224 text = stringResource(R.string.tab_tray_inactive_turn_on_auto_close_button_2),
225 onClick = onEnableAutoCloseClick,
232 @Preview(name = "Auto close dialog dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
233 @Preview(name = "Auto close dialog light", uiMode = Configuration.UI_MODE_NIGHT_NO)
234 private fun InactiveTabsAutoClosePromptPreview() {
236 Box(Modifier.background(FirefoxTheme.colors.layer1)) {
237 InactiveTabsAutoClosePrompt(
239 onEnableAutoCloseClick = {},
246 @Preview(name = "Full preview dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
247 @Preview(name = "Full preview light", uiMode = Configuration.UI_MODE_NIGHT_NO)
248 private fun InactiveTabsListPreview() {
249 var expanded by remember { mutableStateOf(true) }
250 var showAutoClosePrompt by remember { mutableStateOf(true) }
253 Box(Modifier.background(FirefoxTheme.colors.layer1)) {
255 inactiveTabs = generateFakeInactiveTabsList(),
257 showAutoCloseDialog = showAutoClosePrompt,
258 onHeaderClick = { expanded = !expanded },
259 onDeleteAllButtonClick = {},
260 onAutoCloseDismissClick = { showAutoClosePrompt = !showAutoClosePrompt },
261 onEnableAutoCloseClick = { showAutoClosePrompt = !showAutoClosePrompt },
263 onTabCloseClick = {},
269 private fun generateFakeInactiveTabsList(): List<TabSessionState> =
273 content = ContentState(
274 url = "www.mozilla.com",
279 content = ContentState(
280 url = "www.google.com",