From f09dc2453f3257030e7e77ab655f1e7d2504b2c4 Mon Sep 17 00:00:00 2001 From: Colin Lee Date: Fri, 8 Feb 2019 12:35:48 -0600 Subject: [PATCH] Fixes #351 Create home screen component for multitasking --- app/build.gradle | 1 + .../java/org/mozilla/fenix/HomeActivity.kt | 7 +- .../mozilla/fenix/browser/BrowserFragment.kt | 15 +-- .../main/java/org/mozilla/fenix/ext/View.kt | 21 ++++ .../org/mozilla/fenix/home/HomeFragment.kt | 94 +++++++++++++++- .../SessionsLayouts.kt => HomeLayouts.kt} | 24 +++- .../fenix/home/sessions/SessionsComponent.kt | 4 +- .../mozilla/fenix/home/tabs/TabsAdapter.kt | 106 ++++++++++++++++++ .../mozilla/fenix/home/tabs/TabsComponent.kt | 47 ++++++++ .../org/mozilla/fenix/home/tabs/TabsUIView.kt | 49 ++++++++ .../mozilla/fenix/search/SearchFragment.kt | 17 ++- .../search/awesomebar/AwesomeBarComponent.kt | 4 +- .../search/awesomebar/AwesomeBarUIView.kt | 7 +- .../fenix/search/toolbar/ToolbarComponent.kt | 3 +- .../search/toolbar/ToolbarIntegration.kt | 4 +- .../fenix/search/toolbar/ToolbarUIView.kt | 6 +- .../main/res/drawable/ic_add_black_24dp.xml | 9 ++ .../main/res/drawable/ic_close_black_24dp.xml | 9 ++ .../main/res/layout/component_sessions.xml | 9 +- app/src/main/res/layout/component_tabs.xml | 13 +++ app/src/main/res/layout/fragment_home.xml | 12 ++ app/src/main/res/layout/tab_list_header.xml | 40 +++++++ app/src/main/res/layout/tab_list_row.xml | 61 ++++++++++ app/src/main/res/navigation/nav_graph.xml | 1 + app/src/main/res/values/strings.xml | 3 + build.gradle | 1 + buildSrc/src/main/java/Dependencies.kt | 2 + gradle/wrapper/gradle-wrapper.properties | 3 +- 28 files changed, 527 insertions(+), 45 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/ext/View.kt rename app/src/main/java/org/mozilla/fenix/home/{sessions/SessionsLayouts.kt => HomeLayouts.kt} (62%) create mode 100644 app/src/main/java/org/mozilla/fenix/home/tabs/TabsAdapter.kt create mode 100644 app/src/main/java/org/mozilla/fenix/home/tabs/TabsComponent.kt create mode 100644 app/src/main/java/org/mozilla/fenix/home/tabs/TabsUIView.kt create mode 100644 app/src/main/res/drawable/ic_add_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_close_black_24dp.xml create mode 100644 app/src/main/res/layout/component_tabs.xml create mode 100644 app/src/main/res/layout/tab_list_header.xml create mode 100644 app/src/main/res/layout/tab_list_row.xml diff --git a/app/build.gradle b/app/build.gradle index cb72347ce..093739a5c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,6 +6,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply from: "$project.rootDir/automation/gradle/versionCode.gradle" +apply plugin: 'androidx.navigation.safeargs' android { compileSdkVersion 28 diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index dc93510f3..01ce1971d 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -17,7 +17,6 @@ import mozilla.components.concept.engine.EngineView import mozilla.components.feature.intent.IntentProcessor import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.utils.SafeIntent -import org.mozilla.fenix.browser.BrowserFragment import org.mozilla.fenix.ext.components open class HomeActivity : AppCompatActivity() { @@ -71,10 +70,8 @@ open class HomeActivity : AppCompatActivity() { private fun openToBrowser() { val sessionId = SafeIntent(intent).getStringExtra(IntentProcessor.ACTIVE_SESSION_ID) val host = supportFragmentManager.findFragmentById(R.id.container) as NavHostFragment - - host.navController.navigate(R.id.action_global_browser, Bundle().apply { - putString(BrowserFragment.SESSION_ID, sessionId) - }) + val directions = NavGraphDirections.actionGlobalBrowser(sessionId) + host.navController.navigate(directions) } companion object { diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index 1d979fd65..72ba8401a 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -55,17 +55,19 @@ class BrowserFragment : Fragment(), BackHandler { private val findInPageIntegration = ViewBoundFeatureWrapper() private val customTabsToolbarFeature = ViewBoundFeatureWrapper() private val toolbarIntegration = ViewBoundFeatureWrapper() + var sessionId: String? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { + sessionId = BrowserFragmentArgs.fromBundle(arguments!!).sessionId val view = inflater.inflate(R.layout.fragment_browser, container, false) toolbarComponent = ToolbarComponent( view.browserLayout, - ActionBusFactory.get(this), + ActionBusFactory.get(this), sessionId, SearchState("", isEditing = false) ) @@ -98,7 +100,7 @@ class BrowserFragment : Fragment(), BackHandler { .subscribe { when (it) { is SearchAction.ToolbarTapped -> Navigation.findNavController(toolbar) - .navigate(R.id.action_browserFragment_to_searchFragment, null, null) + .navigate(BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(null)) is SearchAction.ToolbarMenuItemTapped -> handleToolbarItemInteraction(it) } } @@ -109,8 +111,6 @@ class BrowserFragment : Fragment(), BackHandler { (activity as AppCompatActivity).supportActionBar?.hide() - val sessionId = arguments?.getString(SESSION_ID) - val sessionManager = requireComponents.core.sessionManager contextMenuFeature.set( @@ -182,8 +182,6 @@ class BrowserFragment : Fragment(), BackHandler { if (sessionFeature.onBackPressed()) return true if (customTabsToolbarFeature.onBackPressed()) return true - // We'll want to improve this when we add multitasking - requireComponents.core.sessionManager.remove() return false } @@ -211,9 +209,9 @@ class BrowserFragment : Fragment(), BackHandler { is ToolbarMenu.Item.Forward -> sessionUseCases.goForward.invoke() is ToolbarMenu.Item.Reload -> sessionUseCases.reload.invoke() is ToolbarMenu.Item.Settings -> Navigation.findNavController(toolbar) - .navigate(R.id.action_browserFragment_to_settingsFragment, null, null) + .navigate(BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment()) is ToolbarMenu.Item.Library -> Navigation.findNavController(toolbar) - .navigate(R.id.action_browserFragment_to_libraryFragment, null, null) + .navigate(BrowserFragmentDirections.actionBrowserFragmentToLibraryFragment()) is ToolbarMenu.Item.RequestDesktop -> sessionUseCases.requestDesktopSite.invoke(action.item.isChecked) is ToolbarMenu.Item.Share -> requireComponents.core.sessionManager .selectedSession?.url?.apply { requireContext().share(this) } @@ -231,7 +229,6 @@ class BrowserFragment : Fragment(), BackHandler { } companion object { - const val SESSION_ID = "session_id" private const val REQUEST_CODE_DOWNLOAD_PERMISSIONS = 1 private const val REQUEST_CODE_PROMPT_PERMISSIONS = 2 private const val TOOLBAR_HEIGHT = 56f diff --git a/app/src/main/java/org/mozilla/fenix/ext/View.kt b/app/src/main/java/org/mozilla/fenix/ext/View.kt new file mode 100644 index 000000000..2611647bc --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/ext/View.kt @@ -0,0 +1,21 @@ +/* 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.ext + +import android.graphics.Rect +import android.view.TouchDelegate +import android.view.View + +fun View?.increaseTapArea(extraDps: Int) { + this!!.post { + val touchRect = Rect() + getHitRect(touchRect) + touchRect.top -= extraDps + touchRect.left -= extraDps + touchRect.right += extraDps + touchRect.bottom += extraDps + (parent as View).touchDelegate = TouchDelegate(touchRect, this) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index c475fae44..86c927488 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.home +import android.annotation.SuppressLint import android.content.res.Resources import android.graphics.drawable.BitmapDrawable import android.os.Bundle @@ -16,28 +17,42 @@ import androidx.fragment.app.Fragment import androidx.navigation.Navigation import kotlinx.android.synthetic.main.fragment_home.* import kotlinx.android.synthetic.main.fragment_home.view.* +import kotlinx.android.synthetic.main.tab_list_header.view.* +import mozilla.components.browser.session.Session +import mozilla.components.browser.session.SessionManager import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R +import org.mozilla.fenix.ext.increaseTapArea import org.mozilla.fenix.ThemeManager import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.home.sessions.SessionsComponent -import org.mozilla.fenix.home.sessions.layoutComponents +import org.mozilla.fenix.home.tabs.TabsAction +import org.mozilla.fenix.home.tabs.TabsChange +import org.mozilla.fenix.home.tabs.TabsComponent +import org.mozilla.fenix.home.tabs.TabsState import org.mozilla.fenix.isPrivate import org.mozilla.fenix.mvi.ActionBusFactory +import org.mozilla.fenix.mvi.getManagedEmitter +import org.mozilla.fenix.mvi.getSafeManagedObservable import kotlin.math.roundToInt class HomeFragment : Fragment() { + private val bus = ActionBusFactory.get(this) + private var sessionObserver: SessionManager.Observer? = null + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_home, container, false) - SessionsComponent(view.homeLayout, ActionBusFactory.get(this)) + TabsComponent(view.homeLayout, bus, TabsState(requireComponents.core.sessionManager.sessions)) + SessionsComponent(view.homeLayout, bus) ActionBusFactory.get(this).logMergedObservables() return view } + @SuppressLint("CheckResult") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -45,20 +60,41 @@ class HomeFragment : Fragment() { layoutComponents(view.homeLayout) + getSafeManagedObservable() + .subscribe { + when (it) { + is TabsAction.Select -> { + requireComponents.core.sessionManager.select(it.session) + val directions = HomeFragmentDirections.actionHomeFragmentToBrowserFragment(it.session.id) + Navigation.findNavController(view).navigate(directions) + } + is TabsAction.Close -> { + requireComponents.core.sessionManager.remove(it.session) + } + } + } + val searchIcon = requireComponents.search.searchEngineManager.getDefaultSearchEngine(requireContext()).let { BitmapDrawable(resources, it.icon) } // Temporary so we can easily test settings view.menuButton.setOnClickListener { - Navigation.findNavController(it).navigate(R.id.action_homeFragment_to_settingsFragment, null, null) + val directions = HomeFragmentDirections.actionHomeFragmentToSettingsFragment() + Navigation.findNavController(it).navigate(directions) } view.toolbar.setCompoundDrawablesWithIntrinsicBounds(searchIcon, null, null, null) val roundToInt = (toolbarPaddingDp * Resources.getSystem().displayMetrics.density).roundToInt() view.toolbar.compoundDrawablePadding = roundToInt view.toolbar.setOnClickListener { it -> - Navigation.findNavController(it).navigate(R.id.action_homeFragment_to_searchFragment, null, null) + val directions = HomeFragmentDirections.actionHomeFragmentToSearchFragment(null) + Navigation.findNavController(it).navigate(directions) + } + view.add_tab_button.increaseTapArea(addTabButtonIncreaseDps) + view.add_tab_button.setOnClickListener { + val directions = HomeFragmentDirections.actionHomeFragmentToSearchFragment(null) + Navigation.findNavController(it).navigate(directions) } // There is currently an issue with visibility changes in ConstraintLayout 2.0.0-alpha3 @@ -85,6 +121,7 @@ class HomeFragment : Fragment() { firstKeyTrigger.conditionallyFire(progress) secondKeyTrigger.conditionallyFire(progress) } + override fun onTransitionCompleted(p0: MotionLayout?, p1: Int) { } }) @@ -148,7 +185,56 @@ class HomeFragment : Fragment() { } } + override fun onResume() { + super.onResume() + sessionObserver = subscribeToSessions() + } + + override fun onPause() { + super.onPause() + sessionObserver?.let { + requireComponents.core.sessionManager.unregister(it) + } + } + + private fun subscribeToSessions(): SessionManager.Observer { + val observer = object : SessionManager.Observer { + override fun onSessionAdded(session: Session) { + super.onSessionAdded(session) + getManagedEmitter().onNext( + TabsChange.Changed(requireComponents.core.sessionManager.sessions)) + } + + override fun onSessionRemoved(session: Session) { + super.onSessionRemoved(session) + getManagedEmitter().onNext( + TabsChange.Changed(requireComponents.core.sessionManager.sessions)) + } + + override fun onSessionSelected(session: Session) { + super.onSessionSelected(session) + getManagedEmitter().onNext( + TabsChange.Changed(requireComponents.core.sessionManager.sessions)) + } + + override fun onSessionsRestored() { + super.onSessionsRestored() + getManagedEmitter().onNext( + TabsChange.Changed(requireComponents.core.sessionManager.sessions)) + } + + override fun onAllSessionsRemoved() { + super.onAllSessionsRemoved() + getManagedEmitter().onNext( + TabsChange.Changed(requireComponents.core.sessionManager.sessions)) + } + } + requireComponents.core.sessionManager.register(observer) + return observer + } + companion object { + const val addTabButtonIncreaseDps = 8 const val toolbarPaddingDp = 12f const val firstKeyTriggerFrame = 55 const val secondKeyTriggerFrame = 90 diff --git a/app/src/main/java/org/mozilla/fenix/home/sessions/SessionsLayouts.kt b/app/src/main/java/org/mozilla/fenix/home/HomeLayouts.kt similarity index 62% rename from app/src/main/java/org/mozilla/fenix/home/sessions/SessionsLayouts.kt rename to app/src/main/java/org/mozilla/fenix/home/HomeLayouts.kt index 6752dead4..a3a8779f6 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessions/SessionsLayouts.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeLayouts.kt @@ -2,27 +2,41 @@ 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.home.sessions +package org.mozilla.fenix.home import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID import kotlinx.android.synthetic.main.component_sessions.* +import kotlinx.android.synthetic.main.component_tabs.* import kotlinx.android.synthetic.main.fragment_home.* +import kotlinx.android.synthetic.main.tab_list_header.* import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.BOTTOM import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.END import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.START import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.TOP import org.jetbrains.anko.constraint.layout.applyConstraintSet -import org.mozilla.fenix.home.HomeFragment fun HomeFragment.layoutComponents(layout: ConstraintLayout) { layout.applyConstraintSet { + tabs_header { + connect( + TOP to BOTTOM of homeDivider, + START to START of tabs_list, + END to END of PARENT_ID + ) + } + tabs_list { + connect( + TOP to BOTTOM of tabs_header, + START to START of PARENT_ID, + END to END of PARENT_ID + ) + } session_list { connect( - BOTTOM to BOTTOM of PARENT_ID, + TOP to BOTTOM of tabs_list, START to START of PARENT_ID, - END to END of PARENT_ID, - TOP to BOTTOM of toolbar_wrapper + END to END of PARENT_ID ) } } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessions/SessionsComponent.kt b/app/src/main/java/org/mozilla/fenix/home/sessions/SessionsComponent.kt index a8eac848c..0468070b4 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessions/SessionsComponent.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessions/SessionsComponent.kt @@ -24,7 +24,7 @@ class SessionsComponent( override val reducer: (SessionsState, SessionsChange) -> SessionsState = { state, change -> when (change) { - is SessionsChange.SessionsChanged -> state // copy state with changes here + is SessionsChange.Changed -> state // copy state with changes here } } @@ -42,5 +42,5 @@ sealed class SessionsAction : Action { } sealed class SessionsChange : Change { - object SessionsChanged : SessionsChange() + object Changed : SessionsChange() } diff --git a/app/src/main/java/org/mozilla/fenix/home/tabs/TabsAdapter.kt b/app/src/main/java/org/mozilla/fenix/home/tabs/TabsAdapter.kt new file mode 100644 index 000000000..a040fcf3f --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/tabs/TabsAdapter.kt @@ -0,0 +1,106 @@ +/* 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.home.tabs + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import io.reactivex.Observer +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.tab_list_row.* +import mozilla.components.browser.session.Session +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.increaseTapArea + +class TabsAdapter(private val actionEmitter: Observer) : + RecyclerView.Adapter() { + + var sessions = listOf() + set(value) { + val diffResult = DiffUtil.calculateDiff(TabsDiffCallback(field, value), true) + field = value + diffResult.dispatchUpdatesTo(this@TabsAdapter) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false) + return TabViewHolder(view, actionEmitter) + } + + override fun getItemViewType(position: Int) = TabViewHolder.LAYOUT_ID + + override fun getItemCount(): Int = sessions.size + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is TabViewHolder -> { + holder.bindSession(sessions[position]) + } + } + } + + private class TabViewHolder( + view: View, + actionEmitter: Observer, + override val containerView: View? = view + ) : + RecyclerView.ViewHolder(view), LayoutContainer { + + var session: Session? = null + + init { + item_tab.setOnClickListener { + actionEmitter.onNext(TabsAction.Select(session!!)) + } + + close_tab_button?.run { + increaseTapArea(closeButtonIncreaseDps) + setOnClickListener { + actionEmitter.onNext(TabsAction.Close(session!!)) + } + } + } + + fun bindSession(session: Session) { + this.session = session + text_url.text = session.url + } + + companion object { + const val closeButtonIncreaseDps = 12 + const val LAYOUT_ID = R.layout.tab_list_row + } + } +} + +class TabsDiffCallback( + private val oldList: List, + private val newList: List +) : DiffUtil.Callback() { + + override fun getOldListSize(): Int = oldList.size + override fun getNewListSize(): Int = newList.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return oldList[oldItemPosition].id == newList[newItemPosition].id + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return oldList[oldItemPosition] == newList[newItemPosition] + } + + override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { + val oldSession = oldList[oldItemPosition] + val newSession = newList[newItemPosition] + val diffBundle = Bundle() + if (oldSession.url != newSession.url) { + diffBundle.putString("url", newSession.url) + } + return if (diffBundle.size() == 0) null else diffBundle + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/tabs/TabsComponent.kt b/app/src/main/java/org/mozilla/fenix/home/tabs/TabsComponent.kt new file mode 100644 index 000000000..e17c733e7 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/tabs/TabsComponent.kt @@ -0,0 +1,47 @@ +/* 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.home.tabs + +import android.view.ViewGroup +import mozilla.components.browser.session.Session +import org.mozilla.fenix.mvi.Action +import org.mozilla.fenix.mvi.ActionBusFactory +import org.mozilla.fenix.mvi.Change +import org.mozilla.fenix.mvi.UIComponent +import org.mozilla.fenix.mvi.ViewState + +class TabsComponent( + private val container: ViewGroup, + bus: ActionBusFactory, + override var initialState: TabsState = TabsState(listOf()) +) : + UIComponent( + bus.getManagedEmitter(TabsAction::class.java), + bus.getSafeManagedObservable(TabsChange::class.java) + ) { + + override val reducer: (TabsState, TabsChange) -> TabsState = { state, change -> + when (change) { + is TabsChange.Changed -> state.copy(sessions = change.sessions) + } + } + + override fun initView() = TabsUIView(container, actionEmitter, changesObservable) + + init { + render(reducer) + } +} + +data class TabsState(val sessions: List) : ViewState + +sealed class TabsAction : Action { + data class Select(val session: Session) : TabsAction() + data class Close(val session: Session) : TabsAction() +} + +sealed class TabsChange : Change { + data class Changed(val sessions: List) : TabsChange() +} diff --git a/app/src/main/java/org/mozilla/fenix/home/tabs/TabsUIView.kt b/app/src/main/java/org/mozilla/fenix/home/tabs/TabsUIView.kt new file mode 100644 index 000000000..ca7dc3099 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/tabs/TabsUIView.kt @@ -0,0 +1,49 @@ +/* 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.home.tabs + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import io.reactivex.Observable +import io.reactivex.Observer +import io.reactivex.functions.Consumer +import org.mozilla.fenix.R +import org.mozilla.fenix.mvi.UIView + +class TabsUIView( + container: ViewGroup, + actionEmitter: Observer, + changesObservable: Observable +) : + UIView(container, actionEmitter, changesObservable) { + + private val header: ConstraintLayout = LayoutInflater.from(container.context) + .inflate(R.layout.tab_list_header, container, true) + .findViewById(R.id.tabs_header) + + override val view: RecyclerView = LayoutInflater.from(container.context) + .inflate(R.layout.component_tabs, container, true) + .findViewById(R.id.tabs_list) + + private val tabsAdapter = TabsAdapter(actionEmitter) + + init { + view.apply { + layoutManager = LinearLayoutManager(container.context) + adapter = tabsAdapter + itemAnimator = DefaultItemAnimator() + } + } + + override fun updateView() = Consumer { + tabsAdapter.sessions = it.sessions + header.visibility = if (it.sessions.isEmpty()) View.GONE else View.VISIBLE + } +} diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt index bfba52c16..e3d10634f 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt @@ -15,10 +15,12 @@ import androidx.navigation.Navigation import kotlinx.android.synthetic.main.fragment_search.view.* import org.mozilla.fenix.R import org.mozilla.fenix.mvi.ActionBusFactory +import org.mozilla.fenix.mvi.getManagedEmitter import org.mozilla.fenix.mvi.getSafeManagedObservable import org.mozilla.fenix.search.awesomebar.AwesomeBarAction import org.mozilla.fenix.search.awesomebar.AwesomeBarChange import org.mozilla.fenix.search.awesomebar.AwesomeBarComponent +import org.mozilla.fenix.search.awesomebar.AwesomeBarState import org.mozilla.fenix.search.toolbar.SearchAction import org.mozilla.fenix.search.toolbar.SearchState import org.mozilla.fenix.search.toolbar.ToolbarComponent @@ -33,13 +35,18 @@ class SearchFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { + val sessionId = SearchFragmentArgs.fromBundle(arguments!!).sessionId val view = inflater.inflate(R.layout.fragment_search, container, false) toolbarComponent = ToolbarComponent( view.toolbar_wrapper, ActionBusFactory.get(this), + sessionId, SearchState("", isEditing = true) ) - awesomeBarComponent = AwesomeBarComponent(view.search_layout, ActionBusFactory.get(this)) + awesomeBarComponent = AwesomeBarComponent( + view.search_layout, ActionBusFactory.get(this), + AwesomeBarState("", sessionId == null) + ) ActionBusFactory.get(this).logMergedObservables() return view } @@ -61,8 +68,7 @@ class SearchFragment : Fragment() { when (it) { is SearchAction.UrlCommitted -> transitionToBrowser() is SearchAction.TextChanged -> { - ActionBusFactory.get(this) - .emit(AwesomeBarChange::class.java, AwesomeBarChange.UpdateQuery(it.query)) + getManagedEmitter().onNext(AwesomeBarChange.UpdateQuery(it.query)) } } } @@ -76,7 +82,8 @@ class SearchFragment : Fragment() { } private fun transitionToBrowser() { - Navigation.findNavController(view!!.search_layout) - .navigate(R.id.action_searchFragment_to_browserFragment, null, null) + val sessionId = SearchFragmentArgs.fromBundle(arguments!!).sessionId + val directions = SearchFragmentDirections.actionSearchFragmentToBrowserFragment(sessionId) + Navigation.findNavController(view!!.search_layout).navigate(directions) } } diff --git a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarComponent.kt b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarComponent.kt index fd86be5be..4c68041b3 100644 --- a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarComponent.kt +++ b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarComponent.kt @@ -11,7 +11,7 @@ import org.mozilla.fenix.mvi.Reducer import org.mozilla.fenix.mvi.UIComponent import org.mozilla.fenix.mvi.ViewState -data class AwesomeBarState(val query: String) : ViewState +data class AwesomeBarState(val query: String, val useNewTab: Boolean = false) : ViewState sealed class AwesomeBarAction : Action { object ItemSelected : AwesomeBarAction() @@ -35,7 +35,7 @@ class AwesomeBarComponent( } } - override fun initView() = AwesomeBarUIView(container, actionEmitter, changesObservable) + override fun initView() = AwesomeBarUIView(initialState.useNewTab, container, actionEmitter, changesObservable) init { render(reducer) diff --git a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarUIView.kt b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarUIView.kt index a2c4e245a..cb1d59846 100644 --- a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarUIView.kt +++ b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarUIView.kt @@ -19,6 +19,7 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.mvi.UIView class AwesomeBarUIView( + useNewTab: Boolean, container: ViewGroup, actionEmitter: Observer, changesObservable: Observable @@ -32,7 +33,11 @@ class AwesomeBarUIView( with(container.context) { view.addProviders(ClipboardSuggestionProvider( this, - components.useCases.sessionUseCases.loadUrl, + if (useNewTab) { + components.useCases.tabsUseCases.addTab + } else { + components.useCases.sessionUseCases.loadUrl + }, getDrawable(R.drawable.ic_link).toBitmap(), getString(R.string.awesomebar_clipboard_title) ) diff --git a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarComponent.kt b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarComponent.kt index 7a2baa1ce..a1226ecb3 100644 --- a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarComponent.kt +++ b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarComponent.kt @@ -20,6 +20,7 @@ import org.mozilla.fenix.mvi.ViewState class ToolbarComponent( private val container: ViewGroup, bus: ActionBusFactory, + private val sessionId: String?, override var initialState: SearchState = SearchState("", false) ) : UIComponent( @@ -33,7 +34,7 @@ class ToolbarComponent( } } - override fun initView() = ToolbarUIView(container, actionEmitter, changesObservable) + override fun initView() = ToolbarUIView(sessionId, container, actionEmitter, changesObservable) init { render(reducer) applyTheme() diff --git a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarIntegration.kt b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarIntegration.kt index 6a259e84b..e38ce5eee 100644 --- a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarIntegration.kt +++ b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarIntegration.kt @@ -16,6 +16,7 @@ import mozilla.components.feature.toolbar.ToolbarFeature import org.mozilla.fenix.DefaultThemeManager import mozilla.components.support.base.feature.LifecycleAwareFeature import org.mozilla.fenix.R +import org.mozilla.fenix.browser.BrowserFragmentDirections import org.mozilla.fenix.ext.application import org.mozilla.fenix.ext.components @@ -38,7 +39,8 @@ class ToolbarIntegration( context.getString(R.string.browser_home_button), visible = { sessionManager.runWithSession(sessionId) { it.isCustomTabSession().not() } } ) { - Navigation.findNavController(toolbar).navigate(R.id.action_browserFragment_to_homeFragment) + Navigation.findNavController(toolbar) + .navigate(BrowserFragmentDirections.actionBrowserFragmentToHomeFragment()) } toolbar.addBrowserAction(home) diff --git a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarUIView.kt b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarUIView.kt index 320b95dcd..8487fe5ed 100644 --- a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarUIView.kt +++ b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarUIView.kt @@ -18,6 +18,7 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.mvi.UIView class ToolbarUIView( + sessionId: String?, container: ViewGroup, actionEmitter: Observer, changesObservable: Observable @@ -51,11 +52,12 @@ class ToolbarUIView( setOnEditListener(object : mozilla.components.concept.toolbar.Toolbar.OnEditListener { override fun onTextChanged(text: String) { + url = text actionEmitter.onNext(SearchAction.TextChanged(text)) } override fun onStopEditing() { - actionEmitter.onNext(SearchAction.UrlCommitted("foo")) + actionEmitter.onNext(SearchAction.UrlCommitted(url)) } }) } @@ -71,7 +73,7 @@ class ToolbarUIView( ShippedDomainsProvider().also { it.initialize(this) }, components.core.historyStorage, components.core.sessionManager, - components.core.sessionManager.selectedSession?.id + sessionId ?: components.core.sessionManager.selectedSession?.id ) } } diff --git a/app/src/main/res/drawable/ic_add_black_24dp.xml b/app/src/main/res/drawable/ic_add_black_24dp.xml new file mode 100644 index 000000000..0258249cc --- /dev/null +++ b/app/src/main/res/drawable/ic_add_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_close_black_24dp.xml b/app/src/main/res/drawable/ic_close_black_24dp.xml new file mode 100644 index 000000000..ede4b7108 --- /dev/null +++ b/app/src/main/res/drawable/ic_close_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/component_sessions.xml b/app/src/main/res/layout/component_sessions.xml index ddab1eff5..148322b0b 100644 --- a/app/src/main/res/layout/component_sessions.xml +++ b/app/src/main/res/layout/component_sessions.xml @@ -1,12 +1,7 @@ \ No newline at end of file + android:layout_height="wrap_content" + android:layout_margin="16dp"/> \ No newline at end of file diff --git a/app/src/main/res/layout/component_tabs.xml b/app/src/main/res/layout/component_tabs.xml new file mode 100644 index 000000000..57ad66e27 --- /dev/null +++ b/app/src/main/res/layout/component_tabs.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index b1dab4b29..482835691 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -74,4 +74,16 @@ android:textSize="14sp" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/tab_list_header.xml b/app/src/main/res/layout/tab_list_header.xml new file mode 100644 index 000000000..a22b6b06d --- /dev/null +++ b/app/src/main/res/layout/tab_list_header.xml @@ -0,0 +1,40 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/tab_list_row.xml b/app/src/main/res/layout/tab_list_row.xml new file mode 100644 index 000000000..78a82b39b --- /dev/null +++ b/app/src/main/res/layout/tab_list_row.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 5aeb2fc2d..85d7f1e0b 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -37,6 +37,7 @@ android:id="@+id/action_searchFragment_to_browserFragment" app:destination="@id/browserFragment" app:popUpTo="@id/homeFragment" /> + Library Settings + Current Session + Close tab + Favorite icon diff --git a/build.gradle b/build.gradle index e313ac697..a5d495aeb 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,7 @@ buildscript { dependencies { classpath Deps.tools_androidgradle classpath Deps.tools_kotlingradle + classpath Deps.androidx_safeargs // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index b80805e74..6f3768f79 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -18,6 +18,7 @@ private object Versions { const val androidx_annotation = "1.0.1" const val androidx_lifecycle = "2.0.0" const val androidx_fragment = "1.1.0-alpha04" + const val androidx_safeargs = "1.0.0-beta01" const val mozilla_android_components = "0.42.0-SNAPSHOT" @@ -99,6 +100,7 @@ object Deps { const val androidx_legacy = "androidx.legacy:legacy-support-v4:${Versions.androidx_legacy}" const val androidx_lifecycle_runtime = "androidx.lifecycle:lifecycle-runtime:${Versions.androidx_lifecycle}" const val androidx_preference = "androidx.preference:preference-ktx:${Versions.androidx_preference}" + const val androidx_safeargs = "android.arch.navigation:navigation-safe-args-gradle-plugin:${Versions.androidx_safeargs}" const val android_arch_navigation = "android.arch.navigation:navigation-fragment:${Versions.android_arch_navigation}" const val android_arch_navigation_ui = "android.arch.navigation:navigation-ui:${Versions.android_arch_navigation}" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f73107db5..161f46ad0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Wed Feb 06 16:31:29 CST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip