1
0
Fork 0

Closes #4602 - Add back transitions

master
Emily Kager 2019-08-07 15:41:52 -07:00 committed by Jeff Boek
parent 4566bd6fba
commit 0b98d43c23
5 changed files with 154 additions and 76 deletions

View File

@ -16,6 +16,7 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.NavHostFragment.findNavController
import androidx.navigation.fragment.findNavController
import androidx.transition.TransitionInflater
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.component_search.*
import kotlinx.android.synthetic.main.fragment_browser.view.*
@ -66,19 +67,21 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler {
private val customTabsIntegration = ViewBoundFeatureWrapper<CustomTabsIntegration>()
private var findBookmarkJob: Job? = null
/*
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Disabled while awaiting a better solution to #3209
postponeEnterTransition()
sharedElementEnterTransition =
TransitionInflater.from(context).inflateTransition(android.R.transition.move).setDuration(
SHARED_TRANSITION_MS
)
TransitionInflater.from(context).inflateTransition(android.R.transition.move)
.setDuration(
SHARED_TRANSITION_MS
)
}
*/
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val view = super.onCreateView(inflater, container, savedInstanceState)
view.browserLayout.transitionName = "$TAB_ITEM_TRANSITION_NAME${getSessionById()?.id}"
@ -96,7 +99,8 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler {
val sessionManager = requireComponents.core.sessionManager
getSessionById()?.let {
quickActionSheetView = QuickActionSheetView(view.nestedScrollQuickAction, browserInteractor)
quickActionSheetView =
QuickActionSheetView(view.nestedScrollQuickAction, browserInteractor)
customTabSessionId?.let { customTabSessionId ->
customTabsIntegration.set(
@ -137,13 +141,17 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler {
requireComponents.core.sessionManager,
view.readerViewControlsBar
) { available ->
if (available) { requireComponents.analytics.metrics.track(Event.ReaderModeAvailable) }
if (available) {
requireComponents.analytics.metrics.track(Event.ReaderModeAvailable)
}
browserStore.apply {
dispatch(QuickActionSheetAction.ReadableStateChange(available))
dispatch(QuickActionSheetAction.ReaderActiveStateChange(
sessionManager.selectedSession?.readerMode ?: false
))
dispatch(
QuickActionSheetAction.ReaderActiveStateChange(
sessionManager.selectedSession?.readerMode ?: false
)
)
}
},
owner = this,
@ -183,7 +191,9 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler {
if (customTabsIntegration.onBackPressed()) return true
getSessionById()?.let { session ->
if (session.source == Session.Source.ACTION_VIEW) requireComponents.core.sessionManager.remove(session)
if (session.source == Session.Source.ACTION_VIEW) requireComponents.core.sessionManager.remove(
session
)
}
return false
}
@ -198,7 +208,8 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler {
quickActionSheetController = DefaultQuickActionSheetController(
context = context!!,
navController = findNavController(),
currentSession = getSessionById() ?: requireComponents.core.sessionManager.selectedSessionOrThrow,
currentSession = getSessionById()
?: requireComponents.core.sessionManager.selectedSessionOrThrow,
appLinksUseCases = requireComponents.useCases.appLinksUseCases,
bookmarkTapped = {
lifecycleScope.launch { bookmarkTapped(it) }
@ -214,7 +225,8 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler {
return if (customTabSessionId != null) Pair(toolbarSize, 0) else Pair(0, toolbarAndQASSize)
}
override fun getAppropriateLayoutGravity() = if (customTabSessionId != null) Gravity.TOP else Gravity.BOTTOM
override fun getAppropriateLayoutGravity() =
if (customTabSessionId != null) Gravity.TOP else Gravity.BOTTOM
private fun themeReaderViewControlsForPrivateMode(view: View) = with(view) {
listOf(
@ -223,7 +235,12 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler {
).map {
findViewById<Button>(it)
}.forEach {
it.setTextColor(ContextCompat.getColorStateList(context, R.color.readerview_private_button_color))
it.setTextColor(
ContextCompat.getColorStateList(
context,
R.color.readerview_private_button_color
)
)
}
listOf(
@ -232,13 +249,19 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler {
).map {
findViewById<RadioButton>(it)
}.forEach {
it.setTextColor(ContextCompat.getColorStateList(context, R.color.readerview_private_radio_color))
it.setTextColor(
ContextCompat.getColorStateList(
context,
R.color.readerview_private_radio_color
)
)
}
}
private suspend fun bookmarkTapped(session: Session) = withContext(IO) {
val bookmarksStorage = requireComponents.core.bookmarksStorage
val existing = bookmarksStorage.getBookmarksWithUrl(session.url).firstOrNull { it.url == session.url }
val existing =
bookmarksStorage.getBookmarksWithUrl(session.url).firstOrNull { it.url == session.url }
if (existing != null) {
// Bookmark exists, go to edit fragment
withContext(Main) {
@ -268,7 +291,9 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler {
.setAction(getString(R.string.edit_bookmark_snackbar_action)) {
nav(
R.id.browserFragment,
BrowserFragmentDirections.actionBrowserFragmentToBookmarkEditFragment(guid)
BrowserFragmentDirections.actionBrowserFragmentToBookmarkEditFragment(
guid
)
)
}
.setText(getString(R.string.bookmark_saved_snackbar))
@ -281,7 +306,11 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler {
private fun subscribeToTabCollections() {
requireComponents.core.tabCollectionStorage.getCollections().observe(this, Observer {
requireComponents.core.tabCollectionStorage.cachedTabCollections = it
getManagedEmitter<SessionControlChange>().onNext(SessionControlChange.CollectionsChange(it))
getManagedEmitter<SessionControlChange>().onNext(
SessionControlChange.CollectionsChange(
it
)
)
})
}

View File

@ -97,6 +97,7 @@ class BrowserToolbarView(
toolbarIntegration = ToolbarIntegration(
this,
view,
container,
menuToolbar,
ShippedDomainsProvider().also { it.initialize(this) },
components.core.historyStorage,

View File

@ -5,8 +5,10 @@
package org.mozilla.fenix.components.toolbar
import android.content.Context
import android.view.ViewGroup
import androidx.navigation.NavOptions
import androidx.navigation.Navigation
import androidx.navigation.fragment.FragmentNavigator
import mozilla.components.browser.domains.autocomplete.DomainAutocompleteProvider
import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.session.runWithSession
@ -27,6 +29,7 @@ import org.mozilla.fenix.utils.Settings
class ToolbarIntegration(
context: Context,
toolbar: BrowserToolbar,
browserLayout: ViewGroup,
toolbarMenu: ToolbarMenu,
domainAutocompleteProvider: DomainAutocompleteProvider,
historyStorage: HistoryStorage,
@ -57,15 +60,13 @@ class ToolbarIntegration(
// We need to dynamically add the options here because if you do it in XML it overwrites
val options = NavOptions.Builder().setPopUpTo(R.id.nav_graph, false)
.setEnterAnim(R.anim.fade_in).build()
val extras = null
// Disabled while awaiting a better solution to #3209
// val extras =
// FragmentNavigator.Extras.Builder()
// .addSharedElement(
// browserLayout,
// "$TAB_ITEM_TRANSITION_NAME${sessionManager.selectedSession?.id}"
// )
// .build()
val extras =
FragmentNavigator.Extras.Builder()
.addSharedElement(
browserLayout,
"$TAB_ITEM_TRANSITION_NAME${sessionManager.selectedSession?.id}"
)
.build()
val navController = Navigation.findNavController(toolbar)
if (!navController.popBackStack(
R.id.homeFragment,
@ -104,7 +105,8 @@ class ToolbarIntegration(
ThemeManager.resolveAttribute(R.attr.primaryText, context), renderStyle = renderStyle
)
)
private var menuPresenter = MenuPresenter(toolbar, context.components.core.sessionManager, sessionId)
private var menuPresenter =
MenuPresenter(toolbar, context.components.core.sessionManager, sessionId)
override fun start() {
menuPresenter.start()

View File

@ -25,10 +25,12 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.FragmentNavigator
import androidx.navigation.fragment.NavHostFragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE
import androidx.transition.TransitionInflater
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.android.synthetic.main.fragment_home.view.*
@ -114,8 +116,10 @@ class HomeFragment : Fragment(), AccountObserver {
viewLifecycleOwner.lifecycleScope.launch {
delay(ANIM_SCROLL_DELAY)
restoreLayoutState()
// startPostponedEnterTransition()
}.invokeOnCompletion { sessionControlComponent.view.viewTreeObserver.removeOnPreDrawListener(this) }
startPostponedEnterTransition()
}.invokeOnCompletion {
sessionControlComponent.view.viewTreeObserver.removeOnPreDrawListener(this)
}
}
return true
}
@ -136,10 +140,10 @@ class HomeFragment : Fragment(), AccountObserver {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Disabled while awaiting a better solution to #3209
// postponeEnterTransition()
// sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
// .setDuration(SHARED_TRANSITION_MS)
postponeEnterTransition()
sharedElementEnterTransition =
TransitionInflater.from(context).inflateTransition(android.R.transition.move)
.setDuration(SHARED_TRANSITION_MS)
val sessionObserver = BrowserSessionsObserver(sessionManager, singleSessionObserver) {
emitSessionChanges()
@ -189,12 +193,11 @@ class HomeFragment : Fragment(), AccountObserver {
}
}
// postponeEnterTransition()
ActionBusFactory.get(this).logMergedObservables()
val activity = activity as HomeActivity
ThemeManager.applyStatusBarTheme(activity.window, activity.themeManager, activity)
postponeEnterTransition()
sessionControlComponent.view.viewTreeObserver.addOnPreDrawListener(preDrawListener)
return view
@ -208,9 +211,7 @@ class HomeFragment : Fragment(), AccountObserver {
homeViewModel?.layoutManagerState?.also { parcelable ->
sessionControlComponent.view.layoutManager?.onRestoreInstanceState(parcelable)
}
val progress = homeViewModel?.motionLayoutProgress
homeLayout?.progress =
if (progress ?: 0F > MOTION_LAYOUT_PROGRESS_ROUND_POINT) 1.0f else 0f
homeLayout?.progress = homeViewModel?.motionLayoutProgress ?: 0F
homeViewModel?.layoutManagerState = null
}
@ -223,7 +224,8 @@ class HomeFragment : Fragment(), AccountObserver {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
val iconSize = resources.getDimension(R.dimen.preference_icon_drawable_size).toInt()
val searchEngine = requireComponents.search.searchEngineManager.getDefaultSearchEngine(requireContext())
val searchEngine =
requireComponents.search.searchEngineManager.getDefaultSearchEngine(requireContext())
val searchIcon = BitmapDrawable(resources, searchEngine.icon)
searchIcon.setBounds(0, 0, iconSize, iconSize)
@ -244,13 +246,11 @@ class HomeFragment : Fragment(), AccountObserver {
invokePendingDeleteJobs()
onboarding.finish()
val directions = HomeFragmentDirections.actionHomeFragmentToSearchFragment(null)
// Disabled while awaiting a better solution to #3209
// val extras =
// FragmentNavigator.Extras.Builder()
// .addSharedElement(toolbar_wrapper, "toolbar_wrapper_transition")
// .build()
// nav(R.id.homeFragment, directions, extras)
nav(R.id.homeFragment, directions)
val extras =
FragmentNavigator.Extras.Builder()
.addSharedElement(toolbar_wrapper, "toolbar_wrapper_transition")
.build()
nav(R.id.homeFragment, directions, extras)
requireComponents.analytics.metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.HOME))
}
@ -268,8 +268,13 @@ class HomeFragment : Fragment(), AccountObserver {
}
if (onboarding.userHasBeenOnboarded()) {
val mode = if (newMode == BrowsingModeManager.Mode.Private) Mode.Private else Mode.Normal
getManagedEmitter<SessionControlChange>().onNext(SessionControlChange.ModeChange(mode))
val mode =
if (newMode == BrowsingModeManager.Mode.Private) Mode.Private else Mode.Normal
getManagedEmitter<SessionControlChange>().onNext(
SessionControlChange.ModeChange(
mode
)
)
}
browsingModeManager.mode = newMode
@ -324,7 +329,11 @@ class HomeFragment : Fragment(), AccountObserver {
onboarding.finish()
val mode = currentMode(context!!)
getManagedEmitter<SessionControlChange>().onNext(SessionControlChange.ModeChange(mode))
getManagedEmitter<SessionControlChange>().onNext(
SessionControlChange.ModeChange(
mode
)
)
}
}
}
@ -344,13 +353,14 @@ class HomeFragment : Fragment(), AccountObserver {
val session = sessionManager.findSessionById(action.sessionId)
sessionManager.select(session!!)
val directions = HomeFragmentDirections.actionHomeFragmentToBrowserFragment(null)
// Disabled while awaiting a better solution to #3209
// val extras =
// FragmentNavigator.Extras.Builder()
// .addSharedElement(action.tabView, "$TAB_ITEM_TRANSITION_NAME${action.sessionId}")
// .build()
// nav(R.id.homeFragment, directions, extras)
nav(R.id.homeFragment, directions)
val extras =
FragmentNavigator.Extras.Builder()
.addSharedElement(
action.tabView,
"$TAB_ITEM_TRANSITION_NAME${action.sessionId}"
)
.build()
nav(R.id.homeFragment, directions, extras)
}
is TabAction.Close -> {
if (pendingSessionDeletion?.deletionJob == null) {
@ -430,7 +440,8 @@ class HomeFragment : Fragment(), AccountObserver {
private fun createDeleteCollectionPrompt(tabCollection: TabCollection) {
context?.let {
AlertDialog.Builder(it).apply {
val message = context.getString(R.string.tab_collection_dialog_message, tabCollection.title)
val message =
context.getString(R.string.tab_collection_dialog_message, tabCollection.title)
setMessage(message)
setNegativeButton(R.string.tab_collection_dialog_negative) { dialog: DialogInterface, _ ->
dialog.cancel()
@ -531,7 +542,10 @@ class HomeFragment : Fragment(), AccountObserver {
}
is CollectionAction.RemoveTab -> {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
requireComponents.core.tabCollectionStorage.removeTabFromCollection(action.collection, action.tab)
requireComponents.core.tabCollectionStorage.removeTabFromCollection(
action.collection,
action.tab
)
}
requireComponents.analytics.metrics.track(Event.CollectionTabRemoved)
}
@ -594,7 +608,11 @@ class HomeFragment : Fragment(), AccountObserver {
private fun subscribeToTabCollections(): Observer<List<TabCollection>> {
return Observer<List<TabCollection>> {
requireComponents.core.tabCollectionStorage.cachedTabCollections = it
getManagedEmitter<SessionControlChange>().onNext(SessionControlChange.CollectionsChange(it))
getManagedEmitter<SessionControlChange>().onNext(
SessionControlChange.CollectionsChange(
it
)
)
}.also { observer ->
requireComponents.core.tabCollectionStorage.getCollections().observe(this, observer)
}
@ -659,7 +677,8 @@ class HomeFragment : Fragment(), AccountObserver {
private fun getListOfSessions(): List<Session> {
val isPrivate = (activity as HomeActivity).browsingModeManager.isPrivate
val notPendingDeletion: (Session) -> Boolean = { it.id != pendingSessionDeletion?.sessionId }
val notPendingDeletion: (Session) -> Boolean =
{ it.id != pendingSessionDeletion?.sessionId }
return sessionManager.filteredSessions(isPrivate, notPendingDeletion)
}
@ -677,10 +696,12 @@ class HomeFragment : Fragment(), AccountObserver {
}
viewModel?.tabs = tabs
val selectedTabs =
tabs.find { tab -> tab.sessionId == selectedTabId } ?: if (tabs.size == 1) tabs[0] else null
tabs.find { tab -> tab.sessionId == selectedTabId }
?: if (tabs.size == 1) tabs[0] else null
val selectedSet = if (selectedTabs == null) mutableSetOf() else mutableSetOf(selectedTabs)
viewModel?.selectedTabs = selectedSet
viewModel?.tabCollections = requireComponents.core.tabCollectionStorage.cachedTabCollections.reversed()
viewModel?.tabCollections =
requireComponents.core.tabCollectionStorage.cachedTabCollections.reversed()
viewModel?.selectedTabCollection = selectedTabCollection
viewModel?.saveCollectionStep =
step ?: viewModel?.getStepForTabsAndCollectionSize() ?: SaveCollectionStep.SelectTabs
@ -743,7 +764,10 @@ class HomeFragment : Fragment(), AccountObserver {
override fun onLoggedOut() = emitAccountChanges()
override fun onProfileUpdated(profile: Profile) = emitAccountChanges()
private fun scrollAndAnimateCollection(tabsAddedToCollectionSize: Int, changedCollection: TabCollection? = null) {
private fun scrollAndAnimateCollection(
tabsAddedToCollectionSize: Int,
changedCollection: TabCollection? = null
) {
if (view != null) {
viewLifecycleOwner.lifecycleScope.launch {
val recyclerView = sessionControlComponent.view
@ -762,10 +786,14 @@ class HomeFragment : Fragment(), AccountObserver {
}
}
val lastVisiblePosition =
(recyclerView.layoutManager as? LinearLayoutManager)?.findLastCompletelyVisibleItemPosition() ?: 0
(recyclerView.layoutManager as? LinearLayoutManager)?.findLastCompletelyVisibleItemPosition()
?: 0
if (lastVisiblePosition < indexOfCollection) {
val onScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
override fun onScrollStateChanged(
recyclerView: RecyclerView,
newState: Int
) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == SCROLL_STATE_IDLE) {
animateCollection(tabsAddedToCollectionSize, indexOfCollection)
@ -784,8 +812,10 @@ class HomeFragment : Fragment(), AccountObserver {
private fun animateCollection(addedTabsSize: Int, indexOfCollection: Int) {
viewLifecycleOwner.lifecycleScope.launch {
val viewHolder = sessionControlComponent.view.findViewHolderForAdapterPosition(indexOfCollection)
val border = (viewHolder as? CollectionViewHolder)?.view?.findViewById<View>(R.id.selected_border)
val viewHolder =
sessionControlComponent.view.findViewHolderForAdapterPosition(indexOfCollection)
val border =
(viewHolder as? CollectionViewHolder)?.view?.findViewById<View>(R.id.selected_border)
val listener = object : Animator.AnimatorListener {
override fun onAnimationCancel(animation: Animator?) {
border?.visibility = View.GONE
@ -799,7 +829,8 @@ class HomeFragment : Fragment(), AccountObserver {
?.start()
}
}
border?.animate()?.alpha(1.0F)?.setStartDelay(ANIM_ON_SCREEN_DELAY)?.setDuration(FADE_ANIM_DURATION)
border?.animate()?.alpha(1.0F)?.setStartDelay(ANIM_ON_SCREEN_DELAY)
?.setDuration(FADE_ANIM_DURATION)
?.setListener(listener)?.start()
}.invokeOnCompletion {
showSavedSnackbar(addedTabsSize)
@ -816,7 +847,8 @@ class HomeFragment : Fragment(), AccountObserver {
} else {
R.string.create_collection_tab_saved
}
FenixSnackbar.make(view, Snackbar.LENGTH_LONG).setText(view.context.getString(stringRes)).show()
FenixSnackbar.make(view, Snackbar.LENGTH_LONG)
.setText(view.context.getString(stringRes)).show()
}
}
}
@ -853,7 +885,6 @@ class HomeFragment : Fragment(), AccountObserver {
private const val SHARED_TRANSITION_MS = 200L
private const val TAB_ITEM_TRANSITION_NAME = "tab_item"
private const val toolbarPaddingDp = 12f
private const val MOTION_LAYOUT_PROGRESS_ROUND_POINT = 0.25f
}
}

View File

@ -19,6 +19,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.transition.TransitionInflater
import kotlinx.android.synthetic.main.fragment_search.*
import kotlinx.android.synthetic.main.fragment_search.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -54,6 +55,12 @@ class SearchFragment : Fragment(), BackHandler {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postponeEnterTransition()
sharedElementEnterTransition =
TransitionInflater.from(context).inflateTransition(android.R.transition.move)
.setDuration(
SHARED_TRANSITION_MS
)
requireComponents.analytics.metrics.track(Event.InteractWithSearchURLArea)
}
@ -101,6 +108,7 @@ class SearchFragment : Fragment(), BackHandler {
(activity as HomeActivity).browsingModeManager.isPrivate
)
startPostponedEnterTransition()
return view
}
@ -191,7 +199,8 @@ class SearchFragment : Fragment(), BackHandler {
// The user has the option to go to 'Shortcuts' -> 'Search engine settings' to modify the default search engine.
// When returning from that settings screen we need to update it to account for any changes.
val currentDefaultEngine = requireComponents.search.searchEngineManager.getDefaultSearchEngine(requireContext())
val currentDefaultEngine =
requireComponents.search.searchEngineManager.getDefaultSearchEngine(requireContext())
if (searchStore.state.defaultEngineSource.searchEngine != currentDefaultEngine) {
searchStore.dispatch(
SearchAction.SelectNewDefaultSearchEngine
@ -232,7 +241,8 @@ class SearchFragment : Fragment(), BackHandler {
}
private fun updateSearchWithLabel(searchState: SearchState) {
searchWithShortcuts.visibility = if (searchState.showShortcutEnginePicker) View.VISIBLE else View.GONE
searchWithShortcuts.visibility =
if (searchState.showShortcutEnginePicker) View.VISIBLE else View.GONE
}
private fun updateSearchShortuctsIcon(searchState: SearchState) {
@ -246,7 +256,11 @@ class SearchFragment : Fragment(), BackHandler {
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
when (requestCode) {
REQUEST_CODE_CAMERA_PERMISSIONS -> qrFeature.withFeature {
it.onPermissionsResult(permissions, grantResults)
@ -270,6 +284,7 @@ class SearchFragment : Fragment(), BackHandler {
}
companion object {
private const val SHARED_TRANSITION_MS = 200L
private const val REQUEST_CODE_CAMERA_PERMISSIONS = 1
}
}