1
0
Fork 0

Closes #347: Adds private browsing logic (#506)

* For #347: Adds private browsing logic

*  For #347: Adds private session explainer

* Adds persistence
master
Sawyer Blatz 2019-02-15 09:31:03 -08:00 committed by GitHub
parent fe74df8b7f
commit e5fe80f928
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 244 additions and 53 deletions

View File

@ -0,0 +1,51 @@
/* 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
import android.preference.PreferenceManager
interface BrowsingModeManager {
enum class Mode {
Normal, Private
}
}
var temporaryModeStorage: BrowsingModeManager.Mode? = null
class DefaultBrowsingModeManager(private val homeActivity: HomeActivity) : BrowsingModeManager {
val isPrivate: Boolean
get() = mode == BrowsingModeManager.Mode.Private
var mode: BrowsingModeManager.Mode
get() = temporaryModeStorage!!
set(value) {
temporaryModeStorage = value
updateTheme(value)
setPreference()
}
private fun updateTheme(mode: BrowsingModeManager.Mode) {
homeActivity.themeManager.apply {
val newTheme = when (mode) {
BrowsingModeManager.Mode.Normal -> ThemeManager.Theme.Light
BrowsingModeManager.Mode.Private -> ThemeManager.Theme.Private
}
setTheme(newTheme)
}
}
private fun setPreference() {
PreferenceManager.getDefaultSharedPreferences(homeActivity)
.edit().putBoolean(homeActivity.getString(R.string.pref_key_private_mode), isPrivate).apply()
}
init {
if (temporaryModeStorage == null) {
mode = when (PreferenceManager.getDefaultSharedPreferences(homeActivity)
.getBoolean(homeActivity.getString(R.string.pref_key_private_mode), false)) {
true -> BrowsingModeManager.Mode.Private
false -> BrowsingModeManager.Mode.Normal
}
}
}
}

View File

@ -29,8 +29,12 @@ open class HomeActivity : AppCompatActivity() {
} }
} }
lateinit var browsingModeManager: DefaultBrowsingModeManager
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
browsingModeManager = DefaultBrowsingModeManager(this)
setContentView(R.layout.activity_home) setContentView(R.layout.activity_home)
setTheme(themeManager.currentTheme) setTheme(themeManager.currentTheme)
@ -85,7 +89,7 @@ open class HomeActivity : AppCompatActivity() {
private fun openToBrowser() { private fun openToBrowser() {
val sessionId = SafeIntent(intent).getStringExtra(IntentProcessor.ACTIVE_SESSION_ID) val sessionId = SafeIntent(intent).getStringExtra(IntentProcessor.ACTIVE_SESSION_ID)
val host = supportFragmentManager.findFragmentById(R.id.container) as NavHostFragment val host = supportFragmentManager.findFragmentById(R.id.container) as NavHostFragment
val directions = NavGraphDirections.actionGlobalBrowser(sessionId) val directions = NavGraphDirections.actionGlobalBrowser(sessionId, browsingModeManager.isPrivate)
host.navController.navigate(directions) host.navController.navigate(directions)
} }

View File

@ -8,7 +8,6 @@ import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager
import android.view.Gravity import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -31,6 +30,7 @@ import mozilla.components.feature.session.SessionFeature
import mozilla.components.feature.session.SessionUseCases import mozilla.components.feature.session.SessionUseCases
import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.BackHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.BrowsingModeManager
import org.mozilla.fenix.DefaultThemeManager import org.mozilla.fenix.DefaultThemeManager
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
@ -56,6 +56,7 @@ class BrowserFragment : Fragment(), BackHandler {
private val findInPageIntegration = ViewBoundFeatureWrapper<FindInPageIntegration>() private val findInPageIntegration = ViewBoundFeatureWrapper<FindInPageIntegration>()
private val customTabsToolbarFeature = ViewBoundFeatureWrapper<CustomTabsToolbarFeature>() private val customTabsToolbarFeature = ViewBoundFeatureWrapper<CustomTabsToolbarFeature>()
private val toolbarIntegration = ViewBoundFeatureWrapper<ToolbarIntegration>() private val toolbarIntegration = ViewBoundFeatureWrapper<ToolbarIntegration>()
private var isPrivate = false
var sessionId: String? = null var sessionId: String? = null
override fun onCreateView( override fun onCreateView(
@ -63,12 +64,16 @@ class BrowserFragment : Fragment(), BackHandler {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
require(arguments != null)
sessionId = BrowserFragmentArgs.fromBundle(arguments!!).sessionId sessionId = BrowserFragmentArgs.fromBundle(arguments!!).sessionId
isPrivate = BrowserFragmentArgs.fromBundle(arguments!!).isPrivateTab
val view = inflater.inflate(R.layout.fragment_browser, container, false) val view = inflater.inflate(R.layout.fragment_browser, container, false)
toolbarComponent = ToolbarComponent( toolbarComponent = ToolbarComponent(
view.browserLayout, view.browserLayout,
ActionBusFactory.get(this), sessionId, ActionBusFactory.get(this), sessionId,
isPrivate,
SearchState("", isEditing = false) SearchState("", isEditing = false)
) )
@ -105,7 +110,8 @@ class BrowserFragment : Fragment(), BackHandler {
when (it) { when (it) {
is SearchAction.ToolbarTapped -> Navigation.findNavController(toolbar) is SearchAction.ToolbarTapped -> Navigation.findNavController(toolbar)
.navigate(BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( .navigate(BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
requireComponents.core.sessionManager.selectedSession?.id requireComponents.core.sessionManager.selectedSession?.id,
(activity as HomeActivity).browsingModeManager.isPrivate
)) ))
is SearchAction.ToolbarMenuItemTapped -> handleToolbarItemInteraction(it) is SearchAction.ToolbarMenuItemTapped -> handleToolbarItemInteraction(it)
} }
@ -224,12 +230,11 @@ class BrowserFragment : Fragment(), BackHandler {
ToolbarMenu.Item.Share -> requireComponents.core.sessionManager ToolbarMenu.Item.Share -> requireComponents.core.sessionManager
.selectedSession?.url?.apply { requireContext().share(this) } .selectedSession?.url?.apply { requireContext().share(this) }
ToolbarMenu.Item.NewPrivateTab -> { ToolbarMenu.Item.NewPrivateTab -> {
PreferenceManager.getDefaultSharedPreferences(context) val directions = BrowserFragmentDirections
.edit().putBoolean( .actionBrowserFragmentToSearchFragment(requireComponents.core.sessionManager.selectedSession?.id,
context!!.getString(R.string.pref_key_private_mode), (activity as HomeActivity).browsingModeManager.isPrivate)
!PreferenceManager.getDefaultSharedPreferences(context) Navigation.findNavController(view!!).navigate(directions)
.getBoolean(context!!.getString(R.string.pref_key_private_mode), false) (activity as HomeActivity).browsingModeManager.mode = BrowsingModeManager.Mode.Private
).apply()
} }
ToolbarMenu.Item.FindInPage -> FindInPageIntegration.launch?.invoke() ToolbarMenu.Item.FindInPage -> FindInPageIntegration.launch?.invoke()
ToolbarMenu.Item.ReportIssue -> requireComponents.core.sessionManager ToolbarMenu.Item.ReportIssue -> requireComponents.core.sessionManager
@ -241,9 +246,11 @@ class BrowserFragment : Fragment(), BackHandler {
// TODO Help // TODO Help
} }
ToolbarMenu.Item.NewTab -> { ToolbarMenu.Item.NewTab -> {
val directions = val directions = BrowserFragmentDirections
BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(null) .actionBrowserFragmentToSearchFragment(null,
(activity as HomeActivity).browsingModeManager.isPrivate)
Navigation.findNavController(view!!).navigate(directions) Navigation.findNavController(view!!).navigate(directions)
(activity as HomeActivity).browsingModeManager.mode = BrowsingModeManager.Mode.Normal
} }
} }
} }

View File

@ -21,6 +21,7 @@ class ToolbarComponent(
private val container: ViewGroup, private val container: ViewGroup,
bus: ActionBusFactory, bus: ActionBusFactory,
private val sessionId: String?, private val sessionId: String?,
private val isPrivate: Boolean,
override var initialState: SearchState = SearchState("", false) override var initialState: SearchState = SearchState("", false)
) : ) :
UIComponent<SearchState, SearchAction, SearchChange>( UIComponent<SearchState, SearchAction, SearchChange>(
@ -34,7 +35,7 @@ class ToolbarComponent(
} }
} }
override fun initView() = ToolbarUIView(sessionId, container, actionEmitter, changesObservable) override fun initView() = ToolbarUIView(sessionId, isPrivate, container, actionEmitter, changesObservable)
init { init {
render(reducer) render(reducer)
applyTheme() applyTheme()

View File

@ -9,6 +9,7 @@ import android.graphics.PorterDuff
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.navigation.Navigation import androidx.navigation.Navigation
import mozilla.components.browser.domains.autocomplete.DomainAutocompleteProvider import mozilla.components.browser.domains.autocomplete.DomainAutocompleteProvider
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.session.runWithSession import mozilla.components.browser.session.runWithSession
import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.browser.toolbar.BrowserToolbar
@ -28,7 +29,8 @@ class ToolbarIntegration(
domainAutocompleteProvider: DomainAutocompleteProvider, domainAutocompleteProvider: DomainAutocompleteProvider,
historyStorage: HistoryStorage, historyStorage: HistoryStorage,
sessionManager: SessionManager, sessionManager: SessionManager,
sessionId: String? = null sessionId: String? = null,
isPrivate: Boolean
) : LifecycleAwareFeature { ) : LifecycleAwareFeature {
init { init {
toolbar.setMenuBuilder(toolbarMenu.menuBuilder) toolbar.setMenuBuilder(toolbarMenu.menuBuilder)
@ -68,10 +70,15 @@ class ToolbarIntegration(
toolbar, toolbar,
context.components.core.sessionManager, context.components.core.sessionManager,
if (sessionId == null) { if (sessionId == null) {
context.components.useCases.tabsUseCases.addTab if (isPrivate) {
context.components.useCases.tabsUseCases.addPrivateTab
} else {
context.components.useCases.tabsUseCases.addTab
}
} else context.components.useCases.sessionUseCases.loadUrl, } else context.components.useCases.sessionUseCases.loadUrl,
{ searchTerms -> if (sessionId == null) { { searchTerms -> if (sessionId == null) {
context.components.useCases.searchUseCases.newTabSearch.invoke(searchTerms) context.components.useCases.searchUseCases.newTabSearch
.invoke(searchTerms, Session.Source.USER_ENTERED, true, isPrivate)
} else context.components.useCases.searchUseCases.defaultSearch.invoke(searchTerms) }, } else context.components.useCases.searchUseCases.defaultSearch.invoke(searchTerms) },
sessionId sessionId
) )

View File

@ -19,6 +19,7 @@ import org.mozilla.fenix.mvi.UIView
class ToolbarUIView( class ToolbarUIView(
sessionId: String?, sessionId: String?,
isPrivate: Boolean,
container: ViewGroup, container: ViewGroup,
actionEmitter: Observer<SearchAction>, actionEmitter: Observer<SearchAction>,
changesObservable: Observable<SearchChange> changesObservable: Observable<SearchChange>
@ -72,7 +73,8 @@ class ToolbarUIView(
ShippedDomainsProvider().also { it.initialize(this) }, ShippedDomainsProvider().also { it.initialize(this) },
components.core.historyStorage, components.core.historyStorage,
components.core.sessionManager, components.core.sessionManager,
sessionId sessionId,
isPrivate
) )
} }
} }

View File

@ -21,9 +21,9 @@ import mozilla.components.browser.menu.BrowserMenu
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.SessionManager
import org.mozilla.fenix.DefaultThemeManager import org.mozilla.fenix.DefaultThemeManager
import org.mozilla.fenix.BrowsingModeManager
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ThemeManager
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.home.sessions.SessionsComponent import org.mozilla.fenix.home.sessions.SessionsComponent
import org.mozilla.fenix.home.tabs.TabsAction import org.mozilla.fenix.home.tabs.TabsAction
@ -47,8 +47,9 @@ class HomeFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
val view = inflater.inflate(R.layout.fragment_home, container, false) val view = inflater.inflate(R.layout.fragment_home, container, false)
TabsComponent(view.homeLayout, bus, TabsState(requireComponents.core.sessionManager.sessions)) TabsComponent(view.homeLayout, bus, (activity as HomeActivity).browsingModeManager.isPrivate,
SessionsComponent(view.homeLayout, bus) TabsState(requireComponents.core.sessionManager.sessions))
SessionsComponent(view.homeLayout, bus, (activity as HomeActivity).browsingModeManager.isPrivate)
layoutComponents(view) layoutComponents(view)
ActionBusFactory.get(this).logMergedObservables() ActionBusFactory.get(this).logMergedObservables()
val activity = activity as HomeActivity val activity = activity as HomeActivity
@ -68,7 +69,8 @@ class HomeFragment : Fragment() {
when (it) { when (it) {
is TabsAction.Select -> { is TabsAction.Select -> {
requireComponents.core.sessionManager.select(it.session) requireComponents.core.sessionManager.select(it.session)
val directions = HomeFragmentDirections.actionHomeFragmentToBrowserFragment(it.session.id) val directions = HomeFragmentDirections.actionHomeFragmentToBrowserFragment(it.session.id,
(activity as HomeActivity).browsingModeManager.isPrivate)
Navigation.findNavController(view).navigate(directions) Navigation.findNavController(view).navigate(directions)
} }
is TabsAction.Close -> { is TabsAction.Close -> {
@ -90,8 +92,9 @@ class HomeFragment : Fragment() {
view.toolbar.setCompoundDrawablesWithIntrinsicBounds(searchIcon, null, null, null) view.toolbar.setCompoundDrawablesWithIntrinsicBounds(searchIcon, null, null, null)
val roundToInt = (toolbarPaddingDp * Resources.getSystem().displayMetrics.density).roundToInt() val roundToInt = (toolbarPaddingDp * Resources.getSystem().displayMetrics.density).roundToInt()
view.toolbar.compoundDrawablePadding = roundToInt view.toolbar.compoundDrawablePadding = roundToInt
view.toolbar.setOnClickListener { view.toolbar.setOnClickListener { it ->
val directions = HomeFragmentDirections.actionHomeFragmentToSearchFragment(null) val directions = HomeFragmentDirections.actionHomeFragmentToSearchFragment(null,
(activity as HomeActivity).browsingModeManager.isPrivate)
Navigation.findNavController(it).navigate(directions) Navigation.findNavController(it).navigate(directions)
} }
@ -129,14 +132,10 @@ class HomeFragment : Fragment() {
.isPrivate() .isPrivate()
privateBrowsingButton.setOnClickListener { privateBrowsingButton.setOnClickListener {
// When we build out private mode we will want to handle this logic elsewhere. val browsingModeManager = (activity as HomeActivity).browsingModeManager
(requireActivity() as HomeActivity).themeManager.apply { browsingModeManager.mode = when (browsingModeManager.mode) {
val newTheme = when (this.currentTheme) { BrowsingModeManager.Mode.Normal -> BrowsingModeManager.Mode.Private
ThemeManager.Theme.Light -> ThemeManager.Theme.Private BrowsingModeManager.Mode.Private -> BrowsingModeManager.Mode.Normal
ThemeManager.Theme.Private -> ThemeManager.Theme.Light
}
setTheme(newTheme)
} }
} }
} }
@ -144,6 +143,7 @@ class HomeFragment : Fragment() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
sessionObserver = subscribeToSessions() sessionObserver = subscribeToSessions()
sessionObserver?.onSessionsRestored()
} }
override fun onPause() { override fun onPause() {
@ -170,31 +170,36 @@ class HomeFragment : Fragment() {
override fun onSessionAdded(session: Session) { override fun onSessionAdded(session: Session) {
super.onSessionAdded(session) super.onSessionAdded(session)
getManagedEmitter<TabsChange>().onNext( getManagedEmitter<TabsChange>().onNext(
TabsChange.Changed(requireComponents.core.sessionManager.sessions)) TabsChange.Changed(requireComponents.core.sessionManager.sessions
.filter { (activity as HomeActivity).browsingModeManager.isPrivate == it.private }))
} }
override fun onSessionRemoved(session: Session) { override fun onSessionRemoved(session: Session) {
super.onSessionRemoved(session) super.onSessionRemoved(session)
getManagedEmitter<TabsChange>().onNext( getManagedEmitter<TabsChange>().onNext(
TabsChange.Changed(requireComponents.core.sessionManager.sessions)) TabsChange.Changed(requireComponents.core.sessionManager.sessions
.filter { (activity as HomeActivity).browsingModeManager.isPrivate == it.private }))
} }
override fun onSessionSelected(session: Session) { override fun onSessionSelected(session: Session) {
super.onSessionSelected(session) super.onSessionSelected(session)
getManagedEmitter<TabsChange>().onNext( getManagedEmitter<TabsChange>().onNext(
TabsChange.Changed(requireComponents.core.sessionManager.sessions)) TabsChange.Changed(requireComponents.core.sessionManager.sessions
.filter { (activity as HomeActivity).browsingModeManager.isPrivate == it.private }))
} }
override fun onSessionsRestored() { override fun onSessionsRestored() {
super.onSessionsRestored() super.onSessionsRestored()
getManagedEmitter<TabsChange>().onNext( getManagedEmitter<TabsChange>().onNext(
TabsChange.Changed(requireComponents.core.sessionManager.sessions)) TabsChange.Changed(requireComponents.core.sessionManager.sessions
.filter { (activity as HomeActivity).browsingModeManager.isPrivate == it.private }))
} }
override fun onAllSessionsRemoved() { override fun onAllSessionsRemoved() {
super.onAllSessionsRemoved() super.onAllSessionsRemoved()
getManagedEmitter<TabsChange>().onNext( getManagedEmitter<TabsChange>().onNext(
TabsChange.Changed(requireComponents.core.sessionManager.sessions)) TabsChange.Changed(requireComponents.core.sessionManager.sessions
.filter { (activity as HomeActivity).browsingModeManager.isPrivate == it.private }))
} }
} }
requireComponents.core.sessionManager.register(observer) requireComponents.core.sessionManager.register(observer)

View File

@ -4,6 +4,10 @@
package org.mozilla.fenix.home.sessions package org.mozilla.fenix.home.sessions
import android.content.Context
import android.text.SpannableString
import android.text.style.ClickableSpan
import android.text.style.ForegroundColorSpan
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -12,38 +16,70 @@ import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.R import org.mozilla.fenix.R
class SessionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { class SessionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var isPrivate = false
var context: Context? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false) val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return when (viewType) { return when (viewType) {
HeaderViewHolder.LAYOUT_ID -> HeaderViewHolder(view) HeaderViewHolder.LAYOUT_ID -> HeaderViewHolder(view)
EmptyListViewHolder.LAYOUT_ID -> EmptyListViewHolder(view) EmptyListViewHolder.LAYOUT_ID -> EmptyListViewHolder(view)
PrivateEmptyListViewHolder.LAYOUT_ID -> PrivateEmptyListViewHolder(view)
else -> EmptyListViewHolder(view) else -> EmptyListViewHolder(view)
} }
} }
override fun getItemViewType(position: Int) = when (position) { override fun getItemViewType(position: Int) = when (position) {
0 -> HeaderViewHolder.LAYOUT_ID 0 -> HeaderViewHolder.LAYOUT_ID
1 -> EmptyListViewHolder.LAYOUT_ID 1 -> if (isPrivate) PrivateEmptyListViewHolder.LAYOUT_ID else EmptyListViewHolder.LAYOUT_ID
else -> -1 else -> -1
} }
override fun getItemCount(): Int = 2 override fun getItemCount(): Int = 2
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is HeaderViewHolder) { when (holder) {
holder.headerText.text = "Today" is HeaderViewHolder -> if (isPrivate) {
holder.headerText.text = "Private Session"
} else {
holder.headerText.text = "Today"
}
is PrivateEmptyListViewHolder -> {
// Format the description text to include a hyperlink
val descriptionText = String
.format(holder.description.text.toString(), System.getProperty("line.separator"))
val linkStartIndex = descriptionText.indexOf("\n\n") + 2
val linkAction = object : ClickableSpan() {
override fun onClick(widget: View?) {
// TODO Go to SUMO page
}
}
val textWithLink = SpannableString(descriptionText).apply {
setSpan(linkAction, linkStartIndex, descriptionText.length, 0)
val colorSpan = ForegroundColorSpan(holder.description.currentTextColor)
setSpan(colorSpan, linkStartIndex, descriptionText.length, 0)
}
holder.description.text = textWithLink
}
} }
} }
private class HeaderViewHolder(private val view: View) : RecyclerView.ViewHolder(view) { private class HeaderViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val headerText = view.findViewById<TextView>(R.id.header_text) val headerText = view.findViewById<TextView>(R.id.header_text)
companion object { companion object {
const val LAYOUT_ID = R.layout.session_list_header const val LAYOUT_ID = R.layout.session_list_header
} }
} }
private class PrivateEmptyListViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val description = view.findViewById<TextView>(R.id.session_description)
companion object {
const val LAYOUT_ID = R.layout.session_list_empty_private
}
}
private class EmptyListViewHolder(private val view: View) : RecyclerView.ViewHolder(view) { private class EmptyListViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
companion object { companion object {
const val LAYOUT_ID = R.layout.session_list_empty const val LAYOUT_ID = R.layout.session_list_empty

View File

@ -15,6 +15,7 @@ import org.mozilla.fenix.mvi.ViewState
class SessionsComponent( class SessionsComponent(
private val container: ViewGroup, private val container: ViewGroup,
bus: ActionBusFactory, bus: ActionBusFactory,
private val isPrivate: Boolean,
override var initialState: SessionsState = SessionsState(emptyList()) override var initialState: SessionsState = SessionsState(emptyList())
) : ) :
UIComponent<SessionsState, SessionsAction, SessionsChange>( UIComponent<SessionsState, SessionsAction, SessionsChange>(
@ -28,19 +29,19 @@ class SessionsComponent(
} }
} }
override fun initView() = SessionsUIView(container, actionEmitter, changesObservable) override fun initView() = SessionsUIView(container, actionEmitter, isPrivate, changesObservable)
init { init {
render(reducer) render(reducer)
} }
} }
data class SessionsState(val sessions: List<Session>) : ViewState data class SessionsState(val sessions: List<Session>, val isPrivate: Boolean = false) : ViewState
sealed class SessionsAction : Action { sealed class SessionsAction : Action {
object Select : SessionsAction() object Select : SessionsAction()
} }
sealed class SessionsChange : Change { sealed class SessionsChange : Change {
object Changed : SessionsChange() data class Changed(val isPrivate: Boolean) : SessionsChange()
} }

View File

@ -17,6 +17,7 @@ import org.mozilla.fenix.mvi.UIView
class SessionsUIView( class SessionsUIView(
container: ViewGroup, container: ViewGroup,
actionEmitter: Observer<SessionsAction>, actionEmitter: Observer<SessionsAction>,
isPrivate: Boolean,
changesObservable: Observable<SessionsChange> changesObservable: Observable<SessionsChange>
) : ) :
UIView<SessionsState, SessionsAction, SessionsChange>(container, actionEmitter, changesObservable) { UIView<SessionsState, SessionsAction, SessionsChange>(container, actionEmitter, changesObservable) {
@ -30,6 +31,8 @@ class SessionsUIView(
init { init {
view.apply { view.apply {
layoutManager = LinearLayoutManager(container.context) layoutManager = LinearLayoutManager(container.context)
sessionsAdapter.isPrivate = isPrivate
sessionsAdapter.context = context
adapter = sessionsAdapter adapter = sessionsAdapter
} }
} }

View File

@ -15,6 +15,7 @@ import org.mozilla.fenix.mvi.ViewState
class TabsComponent( class TabsComponent(
private val container: ViewGroup, private val container: ViewGroup,
bus: ActionBusFactory, bus: ActionBusFactory,
private val isPrivate: Boolean,
override var initialState: TabsState = TabsState(listOf()) override var initialState: TabsState = TabsState(listOf())
) : ) :
UIComponent<TabsState, TabsAction, TabsChange>( UIComponent<TabsState, TabsAction, TabsChange>(
@ -28,7 +29,7 @@ class TabsComponent(
} }
} }
override fun initView() = TabsUIView(container, actionEmitter, changesObservable) override fun initView() = TabsUIView(container, actionEmitter, isPrivate, changesObservable)
init { init {
render(reducer) render(reducer)

View File

@ -28,6 +28,7 @@ import org.mozilla.fenix.mvi.UIView
class TabsUIView( class TabsUIView(
container: ViewGroup, container: ViewGroup,
actionEmitter: Observer<TabsAction>, actionEmitter: Observer<TabsAction>,
isPrivate: Boolean,
changesObservable: Observable<TabsChange> changesObservable: Observable<TabsChange>
) : ) :
UIView<TabsState, TabsAction, TabsChange>(container, actionEmitter, changesObservable) { UIView<TabsState, TabsAction, TabsChange>(container, actionEmitter, changesObservable) {
@ -50,7 +51,8 @@ class TabsUIView(
} }
header.add_tab_button.increaseTapArea(HomeFragment.addTabButtonIncreaseDps) header.add_tab_button.increaseTapArea(HomeFragment.addTabButtonIncreaseDps)
header.add_tab_button.setOnClickListener { header.add_tab_button.setOnClickListener {
val directions = HomeFragmentDirections.actionHomeFragmentToSearchFragment(null) val directions = HomeFragmentDirections.actionHomeFragmentToSearchFragment(null,
isPrivate)
Navigation.findNavController(it).navigate(directions) Navigation.findNavController(it).navigate(directions)
} }
header.tabs_overflow_button.increaseTapArea(HomeFragment.overflowButtonIncreaseDps) header.tabs_overflow_button.increaseTapArea(HomeFragment.overflowButtonIncreaseDps)

View File

@ -21,6 +21,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.ActionBusFactory
@ -57,7 +58,8 @@ class HistoryFragment : Fragment(), CoroutineScope {
if (it is HistoryAction.Select) { if (it is HistoryAction.Select) {
Navigation.findNavController(requireActivity(), R.id.container).apply { Navigation.findNavController(requireActivity(), R.id.container).apply {
navigate( navigate(
HistoryFragmentDirections.actionGlobalBrowser(null), HistoryFragmentDirections.actionGlobalBrowser(null,
(activity as HomeActivity).browsingModeManager.isPrivate),
NavOptions.Builder().setPopUpTo(R.id.homeFragment, false).build() NavOptions.Builder().setPopUpTo(R.id.homeFragment, false).build()
) )
} }

View File

@ -13,6 +13,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.Navigation import androidx.navigation.Navigation
import kotlinx.android.synthetic.main.fragment_search.view.* import kotlinx.android.synthetic.main.fragment_search.view.*
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.getManagedEmitter import org.mozilla.fenix.mvi.getManagedEmitter
@ -36,11 +37,13 @@ class SearchFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
val sessionId = SearchFragmentArgs.fromBundle(arguments!!).sessionId val sessionId = SearchFragmentArgs.fromBundle(arguments!!).sessionId
val isPrivate = SearchFragmentArgs.fromBundle(arguments!!).isPrivateTab
val view = inflater.inflate(R.layout.fragment_search, container, false) val view = inflater.inflate(R.layout.fragment_search, container, false)
toolbarComponent = ToolbarComponent( toolbarComponent = ToolbarComponent(
view.toolbar_wrapper, view.toolbar_wrapper,
ActionBusFactory.get(this), ActionBusFactory.get(this),
sessionId, sessionId,
isPrivate,
SearchState("", isEditing = true) SearchState("", isEditing = true)
) )
awesomeBarComponent = AwesomeBarComponent( awesomeBarComponent = AwesomeBarComponent(
@ -83,7 +86,9 @@ class SearchFragment : Fragment() {
private fun transitionToBrowser() { private fun transitionToBrowser() {
val sessionId = SearchFragmentArgs.fromBundle(arguments!!).sessionId val sessionId = SearchFragmentArgs.fromBundle(arguments!!).sessionId
val directions = SearchFragmentDirections.actionSearchFragmentToBrowserFragment(sessionId) val directions = SearchFragmentDirections.actionSearchFragmentToBrowserFragment(sessionId,
(activity as HomeActivity).browsingModeManager.isPrivate)
Navigation.findNavController(view!!.search_layout).navigate(directions) Navigation.findNavController(view!!.search_layout).navigate(directions)
} }
} }

View File

@ -88,7 +88,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
Navigation.findNavController(it) Navigation.findNavController(it)
.navigate( .navigate(
SettingsFragmentDirections.actionGlobalBrowser( SettingsFragmentDirections.actionGlobalBrowser(
null null, false
) )
) )
} }

View File

@ -0,0 +1,11 @@
<?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" >
<solid android:color="@color/photonGrey40" />
<corners
android:bottomLeftRadius="7dp"
android:bottomRightRadius="7dp" />
</shape>

View File

@ -0,0 +1,13 @@
<?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">
<stroke android:width="2dp"
android:color="?attr/sessionBorderColor"/>
<padding android:left="1dp" android:top="1dp" android:right="1dp" android:bottom="1dp"/>
<corners android:radius="8dp"/>
</shape>

View File

@ -0,0 +1,24 @@
<?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/. -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/private_no_sessions"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/session_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:ellipsize="none"
android:gravity="center_vertical"
android:scrollHorizontally="false"
android:text="@string/private_browsing_explanation"
android:textColor="@color/off_white"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -5,8 +5,8 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/header_text" android:id="@+id/header_text"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:textSize="12dp" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:textColor="@color/session_list_header" android:textColor="?attr/toolbarTextColor"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
</TextView> </TextView>

View File

@ -16,7 +16,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/tabs_header_title" android:text="@string/tabs_header_title"
android:textSize="24sp" android:textSize="24sp"
android:textColor="@android:color/black" android:textColor="?attr/toolbarTextColor"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/> app:layout_constraintStart_toStartOf="parent"/>
<ImageView <ImageView
@ -25,6 +25,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:src="@drawable/ic_new" android:src="@drawable/ic_new"
android:tint="?attr/toolbarTextColor"
android:baselineAlignBottom="true" android:baselineAlignBottom="true"
app:layout_constraintBaseline_toBaselineOf="@id/header_text" app:layout_constraintBaseline_toBaselineOf="@id/header_text"
app:layout_constraintEnd_toStartOf="@id/tabs_overflow_button"/> app:layout_constraintEnd_toStartOf="@id/tabs_overflow_button"/>
@ -33,6 +34,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:tint="?attr/toolbarTextColor"
android:src="@drawable/ic_menu" android:src="@drawable/ic_menu"
android:baselineAlignBottom="true" android:baselineAlignBottom="true"
app:layout_constraintBaseline_toBaselineOf="@id/header_text" app:layout_constraintBaseline_toBaselineOf="@id/header_text"

View File

@ -8,9 +8,11 @@
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:padding="10dp" android:padding="10dp"
android:elevation="5dp" android:elevation="5dp"
app:cardCornerRadius="10dp"> app:cardCornerRadius="10dp"
android:clipChildren="true">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:background="@drawable/session_border"
android:id="@+id/item_tab" android:id="@+id/item_tab"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -50,7 +52,10 @@
<ImageView android:layout_width="0dp" <ImageView android:layout_width="0dp"
android:layout_height="100dp" android:layout_height="100dp"
android:src="@color/photonBlue40" android:layout_marginStart="1dp"
android:layout_marginEnd="1dp"
android:layout_marginBottom="1dp"
android:background="@drawable/session_background"
app:layout_constraintTop_toBottomOf="@id/text_url" app:layout_constraintTop_toBottomOf="@id/text_url"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@ -38,6 +38,7 @@
app:destination="@id/browserFragment" app:destination="@id/browserFragment"
app:popUpTo="@id/homeFragment" /> app:popUpTo="@id/homeFragment" />
<argument android:name="session_id" app:argType="string" app:nullable="true"/> <argument android:name="session_id" app:argType="string" app:nullable="true"/>
<argument android:name="is_private_tab" app:argType="boolean"/>
</fragment> </fragment>
<fragment <fragment
@ -57,6 +58,9 @@
android:name="session_id" android:name="session_id"
app:argType="string" app:argType="string"
app:nullable="true" /> app:nullable="true" />
<argument
android:name="is_private_tab"
app:argType="boolean"/>
<action <action
android:id="@+id/action_browserFragment_to_settingsFragment" android:id="@+id/action_browserFragment_to_settingsFragment"
app:destination="@id/settingsFragment" /> app:destination="@id/settingsFragment" />

View File

@ -23,6 +23,7 @@
<attr name="toolbarTextColor" format="reference"/> <attr name="toolbarTextColor" format="reference"/>
<attr name="navigationBarColorHome" format="reference"/> <attr name="navigationBarColorHome" format="reference"/>
<attr name="homeDividerColor" format="reference" /> <attr name="homeDividerColor" format="reference" />
<attr name="sessionBorderColor" format="reference"/>
<!-- Search fragment --> <!-- Search fragment -->
<attr name="searchBackground" format="reference"/> <attr name="searchBackground" format="reference"/>

View File

@ -21,6 +21,7 @@
<color name="off_white">#f9f9fa</color> <color name="off_white">#f9f9fa</color>
<color name="url_box_view">#E9E9ED</color> <color name="url_box_view">#E9E9ED</color>
<color name="icons">#20233E</color> <color name="icons">#20233E</color>
<color name="session_border_color">#2f26c1</color>
<color name="session_list_empty_bg">#1A665BFD</color> <color name="session_list_empty_bg">#1A665BFD</color>
<color name="session_list_empty_fg">#544CD9</color> <color name="session_list_empty_fg">#544CD9</color>

View File

@ -21,7 +21,8 @@
<!-- Explanation for private browsing displayed to users on home view when they first enable private mode --> <!-- Explanation for private browsing displayed to users on home view when they first enable private mode -->
<string name="private_browsing_explanation">Fenix clears your search and browsing history when you close all <string name="private_browsing_explanation">Fenix clears your search and browsing history when you close all
Private Sessions tabs. While this doesn\'t make you anonymous to websites or your internet service provider, Private Sessions tabs. While this doesn\'t make you anonymous to websites or your internet service provider,
it makes it easier to keep what you do online private from anyone else who uses this device.</string> it makes it easier to keep what you do online private from anyone else who uses this device.\n\nCommon myths
about private browsing</string>
<!-- Delete session button to erase your history in a private session --> <!-- Delete session button to erase your history in a private session -->
<string name="private_browsing_delete_session">Delete Session</string> <string name="private_browsing_delete_session">Delete Session</string>

View File

@ -24,6 +24,7 @@
<item name="toolbarWrapperBackground">@drawable/home_search_background_light</item> <item name="toolbarWrapperBackground">@drawable/home_search_background_light</item>
<item name="toolbarTextColor">@color/search_text</item> <item name="toolbarTextColor">@color/search_text</item>
<item name="homeDividerColor">@color/photonGrey30</item> <item name="homeDividerColor">@color/photonGrey30</item>
<item name="sessionBorderColor">@color/session_border_color</item>
<!-- Search fragment colors --> <!-- Search fragment colors -->
<item name="searchBackground">@color/off_white</item> <item name="searchBackground">@color/off_white</item>
@ -64,6 +65,7 @@
<item name="toolbarWrapperBackground">@drawable/home_search_background_private</item> <item name="toolbarWrapperBackground">@drawable/home_search_background_private</item>
<item name="toolbarTextColor">@color/off_white</item> <item name="toolbarTextColor">@color/off_white</item>
<item name="homeDividerColor">@color/search_private_background</item> <item name="homeDividerColor">@color/search_private_background</item>
<item name="sessionBorderColor">@color/private_browsing_primary</item>
<!-- Search fragment colors --> <!-- Search fragment colors -->
<item name="searchBackground">@color/private_browsing_bottom_gradient</item> <item name="searchBackground">@color/private_browsing_bottom_gradient</item>