/* 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.settings.logins.fragment import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.view.inputmethod.EditorInfo import android.view.Menu import android.view.MenuInflater import android.widget.FrameLayout import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.Toolbar import androidx.constraintlayout.widget.ConstraintLayout import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import kotlinx.android.synthetic.main.fragment_saved_logins.view.* import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ObsoleteCoroutinesApi import mozilla.components.browser.menu.BrowserMenu import mozilla.components.lib.state.ext.consumeFrom import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.redirectToReAuth import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.settings.logins.LoginsAction import org.mozilla.fenix.settings.logins.LoginsFragmentStore import org.mozilla.fenix.settings.logins.controller.LoginsListController import org.mozilla.fenix.settings.logins.LoginsListState import org.mozilla.fenix.settings.logins.interactor.SavedLoginsInteractor import org.mozilla.fenix.settings.logins.SavedLoginsSortingStrategyMenu import org.mozilla.fenix.settings.logins.view.SavedLoginsListView import org.mozilla.fenix.settings.logins.SortingStrategy import org.mozilla.fenix.settings.logins.controller.SavedLoginsStorageController @SuppressWarnings("TooManyFunctions") class SavedLoginsFragment : Fragment() { private lateinit var savedLoginsStore: LoginsFragmentStore private lateinit var savedLoginsListView: SavedLoginsListView private lateinit var savedLoginsInteractor: SavedLoginsInteractor private lateinit var dropDownMenuAnchorView: View private lateinit var sortingStrategyMenu: SavedLoginsSortingStrategyMenu private lateinit var sortingStrategyPopupMenu: BrowserMenu private lateinit var toolbarChildContainer: FrameLayout private lateinit var sortLoginsMenuRoot: ConstraintLayout private lateinit var loginsListController: LoginsListController private lateinit var savedLoginsStorageController: SavedLoginsStorageController override fun onResume() { super.onResume() activity?.window?.setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE ) initToolbar() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_saved_logins, container, false) savedLoginsStore = StoreProvider.get(this) { LoginsFragmentStore( LoginsListState( isLoading = true, loginList = listOf(), filteredItems = listOf(), searchedForText = null, sortingStrategy = requireContext().settings().savedLoginsSortingStrategy, highlightedItem = requireContext().settings().savedLoginsMenuHighlightedItem, duplicateLogins = listOf() // assume on load there are no dupes ) ) } loginsListController = LoginsListController( loginsFragmentStore = savedLoginsStore, navController = findNavController(), browserNavigator = ::openToBrowserAndLoad, settings = requireContext().settings(), metrics = requireContext().components.analytics.metrics ) savedLoginsStorageController = SavedLoginsStorageController( context = requireContext(), viewLifecycleScope = viewLifecycleOwner.lifecycleScope, navController = findNavController(), loginsFragmentStore = savedLoginsStore ) savedLoginsInteractor = SavedLoginsInteractor( loginsListController, savedLoginsStorageController ) savedLoginsListView = SavedLoginsListView( view.savedLoginsLayout, savedLoginsInteractor ) savedLoginsInteractor.loadAndMapLogins() return view } @ObsoleteCoroutinesApi @ExperimentalCoroutinesApi override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) consumeFrom(savedLoginsStore) { sortingStrategyMenu.updateMenu(savedLoginsStore.state.highlightedItem) savedLoginsListView.update(it) } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.login_list, menu) val searchItem = menu.findItem(R.id.search) val searchView: SearchView = searchItem.actionView as SearchView searchView.imeOptions = EditorInfo.IME_ACTION_DONE searchView.queryHint = getString(R.string.preferences_passwords_saved_logins_search) searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { return false } override fun onQueryTextChange(newText: String?): Boolean { savedLoginsStore.dispatch( LoginsAction.FilterLogins( newText ) ) return false } }) } /** * If we pause this fragment, we want to pop users back to reauth */ override fun onPause() { toolbarChildContainer.removeAllViews() toolbarChildContainer.visibility = View.GONE (activity as HomeActivity).getSupportActionBarAndInflateIfNecessary().setDisplayShowTitleEnabled(true) sortingStrategyPopupMenu.dismiss() redirectToReAuth(listOf(R.id.loginDetailFragment), findNavController().currentDestination?.id) super.onPause() } private fun openToBrowserAndLoad( searchTermOrURL: String, newTab: Boolean, from: BrowserDirection ) = (activity as HomeActivity).openToBrowserAndLoad(searchTermOrURL, newTab, from) private fun initToolbar() { showToolbar(getString(R.string.preferences_passwords_saved_logins)) (activity as HomeActivity).getSupportActionBarAndInflateIfNecessary() .setDisplayShowTitleEnabled(false) toolbarChildContainer = initChildContainerFromToolbar() sortLoginsMenuRoot = inflateSortLoginsMenuRoot() dropDownMenuAnchorView = sortLoginsMenuRoot.findViewById(R.id.drop_down_menu_anchor_view) when (requireContext().settings().savedLoginsSortingStrategy) { is SortingStrategy.Alphabetically -> setupMenu( SavedLoginsSortingStrategyMenu.Item.AlphabeticallySort ) is SortingStrategy.LastUsed -> setupMenu( SavedLoginsSortingStrategyMenu.Item.LastUsedSort ) } } private fun initChildContainerFromToolbar(): FrameLayout { val activity = activity as? AppCompatActivity val toolbar = (activity as HomeActivity).findViewById(R.id.navigationToolbar) return (toolbar.findViewById(R.id.toolbar_child_container) as FrameLayout).apply { visibility = View.VISIBLE } } private fun inflateSortLoginsMenuRoot(): ConstraintLayout { return LayoutInflater.from(context) .inflate(R.layout.saved_logins_sort_items_toolbar_child, toolbarChildContainer, true) .findViewById(R.id.sort_logins_menu_root) } private fun attachMenu() { sortingStrategyPopupMenu = sortingStrategyMenu.menuBuilder.build(requireContext()) sortLoginsMenuRoot.setOnClickListener { sortLoginsMenuRoot.isActivated = true sortingStrategyPopupMenu.show( anchor = dropDownMenuAnchorView, orientation = BrowserMenu.Orientation.DOWN ) { sortLoginsMenuRoot.isActivated = false } } } private fun setupMenu(itemToHighlight: SavedLoginsSortingStrategyMenu.Item) { sortingStrategyMenu = SavedLoginsSortingStrategyMenu( requireContext(), itemToHighlight ) { when (it) { SavedLoginsSortingStrategyMenu.Item.AlphabeticallySort -> { savedLoginsInteractor.onSortingStrategyChanged( SortingStrategy.Alphabetically( requireContext().applicationContext ) ) } SavedLoginsSortingStrategyMenu.Item.LastUsedSort -> { savedLoginsInteractor.onSortingStrategyChanged( SortingStrategy.LastUsed( requireContext().applicationContext ) ) } } } attachMenu() } companion object { const val SORTING_STRATEGY_ALPHABETICALLY = "ALPHABETICALLY" const val SORTING_STRATEGY_LAST_USED = "LAST_USED" } }