Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Commit

Permalink
Merge branch 'main' into issue-19956
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Aug 6, 2021
2 parents c7eb738 + f9d6380 commit 96138f0
Show file tree
Hide file tree
Showing 15 changed files with 244 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ sealed class RecentTabViewDecorator {
val context = itemView.context

itemView.background =
AppCompatResources.getDrawable(context, R.drawable.home_list_row_background)
AppCompatResources.getDrawable(context, R.drawable.card_list_row_background)

return itemView
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDirections
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.component_bookmark.view.*
import kotlinx.android.synthetic.main.fragment_bookmark.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
Expand All @@ -44,6 +42,7 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.databinding.FragmentBookmarkBinding
import org.mozilla.fenix.ext.bookmarkStorage
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.minus
Expand Down Expand Up @@ -72,13 +71,16 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
private var pendingBookmarkDeletionJob: (suspend () -> Unit)? = null
private var pendingBookmarksToDelete: MutableSet<BookmarkNode> = mutableSetOf()

private var _binding: FragmentBookmarkBinding? = null
private val binding get() = _binding!!

private val metrics
get() = context?.components?.analytics?.metrics

override val selectedItems get() = bookmarkStore.state.mode.selectedItems

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_bookmark, container, false)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentBookmarkBinding.inflate(inflater, container, false)

bookmarkStore = StoreProvider.get(this) {
BookmarkFragmentStore(BookmarkFragmentState(null))
Expand All @@ -103,8 +105,8 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
metrics = metrics!!
)

bookmarkView = BookmarkView(view.bookmarkLayout, bookmarkInteractor, findNavController())
bookmarkView.view.bookmark_folders_sign_in.visibility = View.GONE
bookmarkView = BookmarkView(binding.bookmarkLayout, bookmarkInteractor, findNavController())
bookmarkView.binding.bookmarkFoldersSignIn.visibility = View.GONE

viewLifecycleOwner.lifecycle.addObserver(
BookmarkDeselectNavigationListener(
Expand All @@ -114,7 +116,7 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
)
)

return view
return binding.root
}

private fun showSnackBarWithText(text: String) {
Expand All @@ -138,7 +140,7 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
// Don't want to pester user too much with it, and if there are lots of bookmarks present,
// it'll just get visually lost. Inside of the "Desktop Bookmarks" node, it'll nicely stand-out,
// since there are always only three other items in there. It's also the right place contextually.
bookmarkView.view.bookmark_folders_sign_in.isVisible =
bookmarkView.binding.bookmarkFoldersSignIn.isVisible =
it.tree?.guid == BookmarkRoot.Root.id && accountManager.authenticatedAccount() == null
}
}
Expand Down Expand Up @@ -361,6 +363,7 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
override fun onDestroyView() {
super.onDestroyView()
_bookmarkInteractor = null
_binding = null
}

private fun showRemoveFolderDialog(selected: Set<BookmarkNode>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@
package org.mozilla.fenix.library.bookmarks

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.navigation.NavController
import kotlinx.android.synthetic.main.component_bookmark.view.*
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.support.base.feature.UserInteractionHandler
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.ComponentBookmarkBinding
import org.mozilla.fenix.library.LibraryPageView
import org.mozilla.fenix.selection.SelectionInteractor

Expand Down Expand Up @@ -106,22 +105,23 @@ class BookmarkView(
private val navController: NavController
) : LibraryPageView(container), UserInteractionHandler {

val view: View = LayoutInflater.from(container.context)
.inflate(R.layout.component_bookmark, container, true)
val binding = ComponentBookmarkBinding.inflate(
LayoutInflater.from(container.context), container, true
)

private var mode: BookmarkFragmentState.Mode = BookmarkFragmentState.Mode.Normal()
private var tree: BookmarkNode? = null

private val bookmarkAdapter = BookmarkAdapter(view.bookmarks_empty_view, interactor)
private val bookmarkAdapter = BookmarkAdapter(binding.bookmarksEmptyView, interactor)

init {
view.bookmark_list.apply {
binding.bookmarkList.apply {
adapter = bookmarkAdapter
}
view.bookmark_folders_sign_in.setOnClickListener {
binding.bookmarkFoldersSignIn.setOnClickListener {
navController.navigate(NavGraphDirections.actionGlobalTurnOnSync())
}
view.swipe_refresh.setOnRefreshListener {
binding.swipeRefresh.setOnRefreshListener {
interactor.onRequestSync()
}
}
Expand Down Expand Up @@ -150,10 +150,10 @@ class BookmarkView(
)
}
}
view.bookmarks_progress_bar.isVisible = state.isLoading
view.swipe_refresh.isEnabled =
binding.bookmarksProgressBar.isVisible = state.isLoading
binding.swipeRefresh.isEnabled =
state.mode is BookmarkFragmentState.Mode.Normal || state.mode is BookmarkFragmentState.Mode.Syncing
view.swipe_refresh.isRefreshing = state.mode is BookmarkFragmentState.Mode.Syncing
binding.swipeRefresh.isRefreshing = state.mode is BookmarkFragmentState.Mode.Syncing
}

override fun onBackPressed(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import mozilla.components.concept.tabstray.Tab
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.InactiveFooterItemBinding
import org.mozilla.fenix.databinding.InactiveRecentlyClosedItemBinding
import org.mozilla.fenix.databinding.InactiveHeaderItemBinding
import org.mozilla.fenix.databinding.InactiveTabListItemBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.loadIntoView
Expand All @@ -22,9 +23,32 @@ import org.mozilla.fenix.tabstray.browser.AutoCloseInterval.OneMonth
import org.mozilla.fenix.tabstray.browser.AutoCloseInterval.OneWeek

sealed class InactiveTabViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
class HeaderHolder(itemView: View) : InactiveTabViewHolder(itemView) {

class HeaderHolder(
itemView: View,
interactor: InactiveTabsInteractor
) : InactiveTabViewHolder(itemView) {

private val binding = InactiveHeaderItemBinding.bind(itemView)

init {
itemView.apply {
isActivated = InactiveTabsState.isExpanded

setOnClickListener {
val newState = !it.isActivated

interactor.onHeaderClicked(newState)

it.isActivated = newState
binding.chevron.rotation = ROTATION_DEGREE
}
}
}

companion object {
const val LAYOUT_ID = R.layout.inactive_header_item
private const val ROTATION_DEGREE = 180F
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ class InactiveTabsAdapter(
delegate: Observable = ObserverRegistry()
) : Adapter(DiffCallback), TabsTray, Observable by delegate {

internal lateinit var inactiveTabsInteractor: InactiveTabsInteractor

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InactiveTabViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(viewType, parent, false)

return when (viewType) {
HeaderHolder.LAYOUT_ID -> HeaderHolder(view)
HeaderHolder.LAYOUT_ID -> HeaderHolder(view, inactiveTabsInteractor)
TabViewHolder.LAYOUT_ID -> TabViewHolder(view, browserTrayInteractor)
FooterHolder.LAYOUT_ID -> FooterHolder(view)
RecentlyClosedHolder.LAYOUT_ID -> RecentlyClosedHolder(view, browserTrayInteractor)
Expand Down Expand Up @@ -81,12 +83,18 @@ class InactiveTabsAdapter(
}

override fun updateTabs(tabs: Tabs) {
// Early return with an empty list to remove the header/footer items.
if (tabs.list.isEmpty()) {
// Early return with an empty list to remove the header/footer items.
submitList(emptyList())
return
}

// If we have items, but we should be in a collapsed state.
if (!InactiveTabsState.isExpanded) {
submitList(listOf(Item.Header))
return
}

val items = tabs.list.map { Item.Tab(it) }
val footer = Item.Footer(context.autoCloseInterval)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* 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 mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.tabstray.TabsTray
import mozilla.components.feature.tabs.ext.toTabs

class InactiveTabsController(
private val browserStore: BrowserStore,
private val tabFilter: (TabSessionState) -> Boolean,
private val tray: TabsTray
) {
/**
* Updates the inactive card to be expanded to display all the tabs, or collapsed with only
* the title showing.
*/
fun updateCardExpansion(isExpanded: Boolean) {
InactiveTabsState.isExpanded = isExpanded

val tabs = browserStore.state.toTabs { tabFilter.invoke(it) }

tray.updateTabs(tabs)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* 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

interface InactiveTabsInteractor {
fun onHeaderClicked(activated: Boolean)
}

class DefaultInactiveTabsInteractor(
private val controller: InactiveTabsController
) : InactiveTabsInteractor {
override fun onHeaderClicked(activated: Boolean) {
controller.updateCardExpansion(activated)
}
}

/**
* An experimental state holder for [InactiveTabsAdapter] that lives at the application lifetime.
*
* TODO This should be replaced with the AppStore.
*/
object InactiveTabsState {
var isExpanded = true
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package org.mozilla.fenix.tabstray.browser
import android.content.Context
import android.util.AttributeSet
import androidx.recyclerview.widget.ConcatAdapter
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.tabstray.TabViewHolder
import mozilla.components.feature.tabs.tabstray.TabsFeature
import org.mozilla.fenix.FeatureFlags
Expand Down Expand Up @@ -53,20 +54,30 @@ class NormalBrowserTrayList @JvmOverloads constructor(
)
}

/**
* NB: The setup for this feature is a bit complicated without a better dependency injection
* solution to scope it down to just this view.
*/
private val inactiveFeature by lazy {
val tabsAdapter = concatAdapter.inactiveTabsAdapter
val store = context.components.core.store
val tabFilter: (TabSessionState) -> Boolean = filter@{
if (!FeatureFlags.inactiveTabs) {
return@filter false
}
it.isNormalTabInactive(maxActiveTime)
}
val tabsAdapter = concatAdapter.inactiveTabsAdapter.apply {
inactiveTabsInteractor = DefaultInactiveTabsInteractor(
InactiveTabsController(store, tabFilter, this)
)
}

TabsFeature(
tabsAdapter,
context.components.core.store,
store,
selectTabUseCase,
removeTabUseCase,
{ state ->
if (!FeatureFlags.inactiveTabs) {
return@TabsFeature false
}
state.isNormalTabInactive(maxActiveTime)
},
tabFilter,
{}
)
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/layout/collection_home_list_row.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="12dp"
android:background="@drawable/home_list_row_background"
android:background="@drawable/card_list_row_background"
android:clickable="true"
android:clipToPadding="false"
android:elevation="@dimen/home_item_elevation"
Expand Down
14 changes: 13 additions & 1 deletion app/src/main/res/layout/inactive_header_item.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="16dp"
android:background="@drawable/rounded_top_corners"
android:background="@drawable/card_list_row_background"
android:clickable="false"
android:clipToPadding="false"
android:focusable="true"
Expand All @@ -31,4 +31,16 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Inactive tabs" />

<ImageView
android:id="@+id/chevron"
android:layout_width="24dp"
android:layout_height="24dp"
android:rotation="180"
android:contentDescription="@string/tab_menu"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_chevron" />

</androidx.constraintlayout.widget.ConstraintLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class RecentTabViewDecoratorTest {
RecentTabViewDecorator.SingleTabDecoration(view)

verify { view.background = drawable }
assertEquals(R.drawable.home_list_row_background, drawableResCaptor.captured)
assertEquals(R.drawable.card_list_row_background, drawableResCaptor.captured)
} finally {
unmockkStatic(AppCompatResources::class)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* 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 org.junit.Test

class DefaultInactiveTabsInteractorTest {

@Test
fun `WHEN onHeaderClicked THEN updateCardExpansion`() {
val controller: InactiveTabsController = mockk(relaxed = true)
val interactor = DefaultInactiveTabsInteractor(controller)

interactor.onHeaderClicked(true)

verify { controller.updateCardExpansion(true) }
}
}
Loading

0 comments on commit 96138f0

Please sign in to comment.