For #633 - Gives the user the ability to save their session
parent
46789f4c62
commit
416382c3ad
|
@ -23,6 +23,7 @@ import kotlinx.android.synthetic.main.fragment_home.view.*
|
||||||
import mozilla.components.browser.menu.BrowserMenu
|
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 mozilla.components.feature.session.bundling.SessionBundleStorage
|
||||||
import org.mozilla.fenix.BrowsingModeManager
|
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
|
||||||
|
@ -42,6 +43,12 @@ import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
||||||
import org.mozilla.fenix.mvi.getManagedEmitter
|
import org.mozilla.fenix.mvi.getManagedEmitter
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
fun SessionBundleStorage.archive(sessionManager: SessionManager) {
|
||||||
|
save(sessionManager.createSnapshot())
|
||||||
|
sessionManager.removeAll()
|
||||||
|
new()
|
||||||
|
}
|
||||||
|
|
||||||
class HomeFragment : Fragment() {
|
class HomeFragment : Fragment() {
|
||||||
private val bus = ActionBusFactory.get(this)
|
private val bus = ActionBusFactory.get(this)
|
||||||
private var sessionObserver: SessionManager.Observer? = null
|
private var sessionObserver: SessionManager.Observer? = null
|
||||||
|
@ -56,7 +63,9 @@ class HomeFragment : Fragment() {
|
||||||
): View? {
|
): View? {
|
||||||
val view = inflater.inflate(R.layout.fragment_home, container, false)
|
val view = inflater.inflate(R.layout.fragment_home, container, false)
|
||||||
val sessionManager = requireComponents.core.sessionManager
|
val sessionManager = requireComponents.core.sessionManager
|
||||||
tabsComponent = TabsComponent(view.homeContainer, bus, (activity as HomeActivity).browsingModeManager.isPrivate,
|
tabsComponent = TabsComponent(
|
||||||
|
view.homeContainer,
|
||||||
|
bus,
|
||||||
TabsState(sessionManager.sessions.map { it.toSessionViewState(it == sessionManager.selectedSession) })
|
TabsState(sessionManager.sessions.map { it.toSessionViewState(it == sessionManager.selectedSession) })
|
||||||
)
|
)
|
||||||
sessionsComponent = SessionsComponent(view.homeContainer, bus)
|
sessionsComponent = SessionsComponent(view.homeContainer, bus)
|
||||||
|
@ -76,7 +85,7 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
sessionsComponent.view.visibility = if ((activity as HomeActivity).browsingModeManager.isPrivate)
|
sessionsComponent.view.visibility = if ((activity as HomeActivity).browsingModeManager.isPrivate)
|
||||||
View.GONE else View.VISIBLE
|
View.GONE else View.VISIBLE
|
||||||
tabsComponent.view.isNestedScrollingEnabled = false
|
tabsComponent.tabList.isNestedScrollingEnabled = false
|
||||||
sessionsComponent.view.isNestedScrollingEnabled = false
|
sessionsComponent.view.isNestedScrollingEnabled = false
|
||||||
|
|
||||||
val bundles = requireComponents.core.sessionStorage.bundles(temporaryNumberOfSessions)
|
val bundles = requireComponents.core.sessionStorage.bundles(temporaryNumberOfSessions)
|
||||||
|
@ -159,6 +168,9 @@ class HomeFragment : Fragment() {
|
||||||
getAutoDisposeObservable<TabsAction>()
|
getAutoDisposeObservable<TabsAction>()
|
||||||
.subscribe {
|
.subscribe {
|
||||||
when (it) {
|
when (it) {
|
||||||
|
is TabsAction.Archive -> {
|
||||||
|
requireComponents.core.sessionStorage.archive(requireComponents.core.sessionManager)
|
||||||
|
}
|
||||||
is TabsAction.Select -> {
|
is TabsAction.Select -> {
|
||||||
val session = requireComponents.core.sessionManager.findSessionById(it.sessionId)
|
val session = requireComponents.core.sessionManager.findSessionById(it.sessionId)
|
||||||
requireComponents.core.sessionManager.select(session!!)
|
requireComponents.core.sessionManager.select(session!!)
|
||||||
|
@ -177,6 +189,7 @@ class HomeFragment : Fragment() {
|
||||||
.subscribe {
|
.subscribe {
|
||||||
when (it) {
|
when (it) {
|
||||||
is SessionsAction.Select -> {
|
is SessionsAction.Select -> {
|
||||||
|
requireComponents.core.sessionStorage.archive(requireComponents.core.sessionManager)
|
||||||
it.archivedSession.bundle.restoreSnapshot(requireComponents.core.engine)?.apply {
|
it.archivedSession.bundle.restoreSnapshot(requireComponents.core.engine)?.apply {
|
||||||
requireComponents.core.sessionManager.restore(this)
|
requireComponents.core.sessionManager.restore(this)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ package org.mozilla.fenix.home.tabs
|
||||||
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.android.synthetic.main.component_tabs.view.*
|
||||||
import mozilla.components.browser.session.Session
|
import mozilla.components.browser.session.Session
|
||||||
import org.mozilla.fenix.mvi.Action
|
import org.mozilla.fenix.mvi.Action
|
||||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||||
|
@ -16,7 +17,6 @@ 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>(
|
||||||
|
@ -30,9 +30,9 @@ class TabsComponent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() = TabsUIView(container, actionEmitter, isPrivate, changesObservable)
|
override fun initView() = TabsUIView(container, actionEmitter, changesObservable)
|
||||||
val view: RecyclerView
|
val tabList: RecyclerView
|
||||||
get() = uiView.view as RecyclerView
|
get() = uiView.view.tabs_list as RecyclerView
|
||||||
|
|
||||||
init {
|
init {
|
||||||
render(reducer)
|
render(reducer)
|
||||||
|
@ -47,6 +47,7 @@ fun Session.toSessionViewState(selected: Boolean): SessionViewState {
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class TabsAction : Action {
|
sealed class TabsAction : Action {
|
||||||
|
object Archive : TabsAction()
|
||||||
data class Select(val sessionId: String) : TabsAction()
|
data class Select(val sessionId: String) : TabsAction()
|
||||||
data class Close(val sessionId: String) : TabsAction()
|
data class Close(val sessionId: String) : TabsAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,16 +8,15 @@ import android.app.Activity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.navigation.Navigation
|
import androidx.navigation.Navigation
|
||||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Observer
|
import io.reactivex.Observer
|
||||||
import io.reactivex.functions.Consumer
|
import io.reactivex.functions.Consumer
|
||||||
import kotlinx.android.synthetic.main.tab_list_header.view.*
|
import kotlinx.android.synthetic.main.component_tabs.view.*
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.ext.increaseTapArea
|
import org.mozilla.fenix.ext.increaseTapArea
|
||||||
import org.mozilla.fenix.home.HomeFragment
|
import org.mozilla.fenix.home.HomeFragment
|
||||||
|
@ -28,46 +27,57 @@ 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) {
|
||||||
|
|
||||||
private val header: ConstraintLayout = LayoutInflater.from(container.context)
|
override val view: View = 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)
|
.inflate(R.layout.component_tabs, container, true)
|
||||||
.findViewById(R.id.tabs_list)
|
|
||||||
|
|
||||||
private val tabsAdapter = TabsAdapter(actionEmitter)
|
private val tabsAdapter = TabsAdapter(actionEmitter)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
view.apply {
|
view.tabs_list.apply {
|
||||||
layoutManager = LinearLayoutManager(container.context)
|
layoutManager = LinearLayoutManager(container.context)
|
||||||
adapter = tabsAdapter
|
adapter = tabsAdapter
|
||||||
itemAnimator = DefaultItemAnimator()
|
itemAnimator = DefaultItemAnimator()
|
||||||
}
|
}
|
||||||
header.add_tab_button.increaseTapArea(HomeFragment.addTabButtonIncreaseDps)
|
view.apply {
|
||||||
header.add_tab_button.setOnClickListener {
|
add_tab_button.increaseTapArea(HomeFragment.addTabButtonIncreaseDps)
|
||||||
val directions = HomeFragmentDirections.actionHomeFragmentToSearchFragment(null)
|
add_tab_button.setOnClickListener {
|
||||||
Navigation.findNavController(it).navigate(directions)
|
val directions = HomeFragmentDirections.actionHomeFragmentToSearchFragment(null)
|
||||||
}
|
Navigation.findNavController(it).navigate(directions)
|
||||||
header.tabs_overflow_button.increaseTapArea(HomeFragment.overflowButtonIncreaseDps)
|
}
|
||||||
header.tabs_overflow_button.setOnClickListener {
|
tabs_overflow_button.increaseTapArea(HomeFragment.overflowButtonIncreaseDps)
|
||||||
if (view.context as? Activity != null) {
|
tabs_overflow_button.setOnClickListener {
|
||||||
CurrentSessionBottomSheetFragment().show(
|
if (view.context as? Activity != null) {
|
||||||
(view.context as FragmentActivity).supportFragmentManager,
|
CurrentSessionBottomSheetFragment().show(
|
||||||
overflowFragmentTag
|
(view.context as FragmentActivity).supportFragmentManager,
|
||||||
)
|
overflowFragmentTag
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
save_session_button_text.apply {
|
||||||
|
val color = ContextCompat.getColor(context, R.color.photonWhite)
|
||||||
|
val drawable = ContextCompat.getDrawable(context, R.drawable.ic_archive)
|
||||||
|
drawable?.setTint(color)
|
||||||
|
this.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
save_session_button.setOnClickListener {
|
||||||
|
actionEmitter.onNext(TabsAction.Archive)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateView() = Consumer<TabsState> {
|
override fun updateView() = Consumer<TabsState> {
|
||||||
tabsAdapter.sessions = it.sessions
|
tabsAdapter.sessions = it.sessions
|
||||||
header.visibility = if (it.sessions.isEmpty()) View.GONE else View.VISIBLE
|
|
||||||
|
(if (it.sessions.isEmpty()) View.GONE else View.VISIBLE).also { visibility ->
|
||||||
|
view.tabs_header.visibility = visibility
|
||||||
|
view.save_session_button.visibility = visibility
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -1,8 +1,82 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<!-- 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/. -->
|
||||||
|
<merge
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/tabs_list"
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
android:layout_width="match_parent"
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_height="wrap_content"
|
android:id="@+id/tabs_header"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_marginEnd="16dp"/>
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/header_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/tabs_header_title"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
||||||
|
android:textColor="?attr/toolbarTextColor"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/add_tab_button"
|
||||||
|
android:layout_width="@dimen/glyph_button_width"
|
||||||
|
android:layout_height="@dimen/glyph_button_height"
|
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_new"
|
||||||
|
android:tint="?attr/toolbarTextColor"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/tabs_overflow_button"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/tabs_overflow_button"
|
||||||
|
android:layout_width="@dimen/glyph_button_width"
|
||||||
|
android:layout_height="@dimen/glyph_button_height"
|
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_menu"
|
||||||
|
android:tint="?attr/toolbarTextColor"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/tabs_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/save_session_button"
|
||||||
|
android:foreground="?android:attr/selectableItemBackground"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:backgroundTint="@color/photonInk70"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/save_session_button_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/session_save"
|
||||||
|
android:textColor="@color/photonWhite"
|
||||||
|
android:drawableStart="@drawable/ic_archive"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false"
|
||||||
|
android:layout_gravity="center" />
|
||||||
|
</FrameLayout>
|
||||||
|
</merge>
|
|
@ -5,7 +5,7 @@
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/delete_history_button"
|
android:id="@+id/delete_history_button"
|
||||||
android:foreground="?android:attr/selectableItemBackground"
|
android:foreground="?android:attr/selectableItemBackground"
|
||||||
android:background="@drawable/delete_history_background"
|
android:background="@drawable/button_background"
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
android:padding="6dp"
|
android:padding="6dp"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
<?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/tabs_header"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/header_text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/tabs_header_title"
|
|
||||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
|
||||||
android:textColor="?attr/toolbarTextColor"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/add_tab_button"
|
|
||||||
android:layout_width="@dimen/glyph_button_width"
|
|
||||||
android:layout_height="@dimen/glyph_button_height"
|
|
||||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
|
||||||
android:src="@drawable/ic_new"
|
|
||||||
android:tint="?attr/toolbarTextColor"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/tabs_overflow_button"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/tabs_overflow_button"
|
|
||||||
android:layout_width="@dimen/glyph_button_width"
|
|
||||||
android:layout_height="@dimen/glyph_button_height"
|
|
||||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
|
||||||
android:src="@drawable/ic_menu"
|
|
||||||
android:tint="?attr/toolbarTextColor"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -165,6 +165,10 @@ people before profit. Our mission: keep the Internet open and accessible to all.
|
||||||
<!-- Content description (not visible, for screen readers etc.): Title icon for current session menu -->
|
<!-- Content description (not visible, for screen readers etc.): Title icon for current session menu -->
|
||||||
<string name="current_session_image">Current session image</string>
|
<string name="current_session_image">Current session image</string>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Text for the button to save a session -->
|
||||||
|
<string name="session_save">Save Session</string>
|
||||||
|
|
||||||
<!-- Text for the button to delete a single session -->
|
<!-- Text for the button to delete a single session -->
|
||||||
<string name="session_item_delete">Delete</string>
|
<string name="session_item_delete">Delete</string>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue