diff --git a/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherActivity.kt b/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherActivity.kt index eb459e726acc..66dd5a46b6a5 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherActivity.kt @@ -24,6 +24,7 @@ import android.view.MenuItem import android.widget.TextView import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR +import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.widget.Toolbar import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle @@ -46,6 +47,7 @@ import com.duckduckgo.app.pixels.AppPixelName import com.duckduckgo.app.settings.SettingsActivity import com.duckduckgo.app.settings.db.SettingsDataStore import com.duckduckgo.app.statistics.pixels.Pixel +import com.duckduckgo.app.tabs.TabManagerFeatureFlags import com.duckduckgo.app.tabs.model.TabEntity import com.duckduckgo.app.tabs.model.TabSwitcherData.LayoutType import com.duckduckgo.app.tabs.ui.TabSwitcherViewModel.Command @@ -63,12 +65,14 @@ import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.di.scopes.ActivityScope import com.duckduckgo.duckchat.api.DuckChat import com.duckduckgo.duckchat.impl.DuckChatPixelName +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.launch @@ -118,6 +122,9 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine @Inject lateinit var duckChat: DuckChat + @Inject + lateinit var tabManagerFeatureFlags: TabManagerFeatureFlags + private val viewModel: TabSwitcherViewModel by bindViewModel() private val tabsAdapter: TabSwitcherAdapter by lazy { TabSwitcherAdapter(this, webViewPreviewPersister, this, faviconManager, dispatchers) } @@ -131,6 +138,7 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine private lateinit var tabsRecycler: RecyclerView private lateinit var tabItemDecorator: TabItemDecorator private lateinit var toolbar: Toolbar + private lateinit var tabsFab: ExtendedFloatingActionButton private var layoutTypeMenuItem: MenuItem? = null private var layoutType: LayoutType? = null @@ -141,14 +149,28 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine firstTimeLoadingTabsList = savedInstanceState?.getBoolean(KEY_FIRST_TIME_LOADING) ?: true + tabsFab = findViewById(R.id.tabsFab) + extractIntentExtras() configureViewReferences() setupToolbar(toolbar) configureRecycler() + configureFab() configureObservers() configureOnBackPressedListener() } + private fun configureFab() { + if (tabManagerFeatureFlags.multiSelection().isEnabled()) { + tabsFab.show() + tabsFab.setOnClickListener { + viewModel.onFabClicked() + } + } else { + tabsFab.hide() + } + } + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) @@ -185,6 +207,21 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine tabItemDecorator = TabItemDecorator(this, selectedTabId) tabsRecycler.addItemDecoration(tabItemDecorator) tabsRecycler.setHasFixedSize(true) + + if (tabManagerFeatureFlags.multiSelection().isEnabled()) { + tabsRecycler.addOnScrollListener( + object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + if (dy > 0) { + tabsFab.shrink() + } else if (dy < 0) { + tabsFab.extend() + } + } + }, + ) + } } private fun configureObservers() { @@ -214,6 +251,12 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine } } + lifecycleScope.launch { + viewModel.viewState.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collectLatest { + updateFabType(it.fabType) + } + } + viewModel.command.observe(this) { processCommand(it) } @@ -254,6 +297,19 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine tabsRecycler.show() } + private fun updateFabType(fabType: TabSwitcherViewModel.ViewState.FabType) { + when (fabType) { + TabSwitcherViewModel.ViewState.FabType.NEW_TAB -> { + tabsFab.icon = AppCompatResources.getDrawable(this, com.duckduckgo.mobile.android.R.drawable.ic_add_24) + tabsFab.setText(R.string.tabSwitcherFabNewTab) + } + TabSwitcherViewModel.ViewState.FabType.CLOSE_TABS -> { + tabsFab.icon = AppCompatResources.getDrawable(this, com.duckduckgo.mobile.android.R.drawable.ic_close_24) + tabsFab.setText(R.string.tabSwitcherFabCloseTabs) + } + } + } + private fun scrollToPreviousCenterOffset(centerOffsetPercent: Float) { tabsRecycler.post { val newRange = tabsRecycler.computeVerticalScrollRange() diff --git a/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherViewModel.kt b/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherViewModel.kt index 6029cb9cc24a..5ac2e3616c21 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherViewModel.kt @@ -32,14 +32,18 @@ import com.duckduckgo.app.tabs.model.TabEntity import com.duckduckgo.app.tabs.model.TabRepository import com.duckduckgo.app.tabs.model.TabSwitcherData.LayoutType.GRID import com.duckduckgo.app.tabs.model.TabSwitcherData.LayoutType.LIST +import com.duckduckgo.app.tabs.ui.TabSwitcherViewModel.ViewState.FabType import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.common.utils.SingleLiveEvent import com.duckduckgo.di.scopes.ActivityScope import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @ContributesViewModel(ActivityScope::class) @@ -65,6 +69,9 @@ class TabSwitcherViewModel @Inject constructor( val command: SingleLiveEvent = SingleLiveEvent() + private val _viewState = MutableStateFlow(ViewState()) + val viewState = _viewState.asStateFlow() + sealed class Command { data object Close : Command() data object CloseAllTabsRequest : Command() @@ -188,4 +195,21 @@ class TabSwitcherViewModel @Inject constructor( tabRepository.setTabLayoutType(newLayoutType) } } + + fun onFabClicked() { + if (viewState.value.fabType == FabType.NEW_TAB) { + _viewState.update { it.copy(FabType.CLOSE_TABS) } + } else { + _viewState.update { it.copy(FabType.NEW_TAB) } + } + } + + data class ViewState( + val fabType: FabType = FabType.NEW_TAB, + ) { + enum class FabType { + NEW_TAB, + CLOSE_TABS, + } + } } diff --git a/app/src/main/res/layout/activity_tab_switcher.xml b/app/src/main/res/layout/activity_tab_switcher.xml index b52eb44aecc4..eadea9c1cbf9 100644 --- a/app/src/main/res/layout/activity_tab_switcher.xml +++ b/app/src/main/res/layout/activity_tab_switcher.xml @@ -14,27 +14,44 @@ ~ limitations under the License. --> - - - - + android:orientation="vertical"> + + + + + + + + - \ No newline at end of file + diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 004f783907ee..5e964b2b3317 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -76,4 +76,10 @@ The government may be blocking access to duckduckgo.com on this network provider, which could affect this app\'s functionality. Other providers may not be affected. Okay + New Tab + Close Tabs + + + Set As Default Browser + diff --git a/common/common-ui/src/main/res/values/design-system-colors.xml b/common/common-ui/src/main/res/values/design-system-colors.xml index 83d2034ca5e5..69c0ceb8ecdc 100644 --- a/common/common-ui/src/main/res/values/design-system-colors.xml +++ b/common/common-ui/src/main/res/values/design-system-colors.xml @@ -162,7 +162,8 @@ #243969EF #2B55CA #1E42A4 - #803969EF + #803969FF + #9CB4FF #D6000000 #99000000 @@ -322,4 +323,4 @@ 2% — 05 1% — 03 0% — 00 - --> \ No newline at end of file + --> diff --git a/common/common-ui/src/main/res/values/design-system-dimensions.xml b/common/common-ui/src/main/res/values/design-system-dimensions.xml index 5307660197ab..a533da8b6048 100644 --- a/common/common-ui/src/main/res/values/design-system-dimensions.xml +++ b/common/common-ui/src/main/res/values/design-system-dimensions.xml @@ -72,6 +72,9 @@ 72dp 170dp + + 56dp + 200dp 24dp diff --git a/common/common-ui/src/main/res/values/design-system-theming.xml b/common/common-ui/src/main/res/values/design-system-theming.xml index 2dc6f02ed788..e14613382159 100644 --- a/common/common-ui/src/main/res/values/design-system-theming.xml +++ b/common/common-ui/src/main/res/values/design-system-theming.xml @@ -55,6 +55,7 @@ @style/Widget.DuckDuckGo.TabLayout @style/Widget.DuckDuckGo.RadioButton @style/Widget.DuckDuckGo.CheckBox + @style/Widget.DuckDuckGo.ExtensibleFloatingActionButton @style/Widget.DuckDuckGo.Slider @style/Widget.DuckDuckGo.v3.Switch @style/Widget.DuckDuckGo.Snackbar diff --git a/common/common-ui/src/main/res/values/widget-colors.xml b/common/common-ui/src/main/res/values/widget-colors.xml index b44af216f0ca..f46bf853f4aa 100644 --- a/common/common-ui/src/main/res/values/widget-colors.xml +++ b/common/common-ui/src/main/res/values/widget-colors.xml @@ -41,4 +41,7 @@ @color/black84 @color/black60 @color/blue50 + @color/blue10_solid + @color/blue70 + @color/blue70 diff --git a/common/common-ui/src/main/res/values/widgets.xml b/common/common-ui/src/main/res/values/widgets.xml index cbbaee121f60..1d2831fbd360 100644 --- a/common/common-ui/src/main/res/values/widgets.xml +++ b/common/common-ui/src/main/res/values/widgets.xml @@ -207,6 +207,20 @@ @dimen/keyline_2 + + + +