/* 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.browser import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.widget.Button import android.widget.ImageView import android.widget.PopupWindow import android.widget.RadioButton import androidx.appcompat.widget.AppCompatImageView import androidx.core.content.ContextCompat import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.transition.TransitionInflater import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_browser.view.* import kotlinx.android.synthetic.main.tracking_protection_onboarding_popup.view.* import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ObsoleteCoroutinesApi import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import mozilla.appservices.places.BookmarkRoot import mozilla.components.browser.session.Session import mozilla.components.feature.readerview.ReaderViewFeature import mozilla.components.feature.sitepermissions.SitePermissions import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import org.jetbrains.anko.dimen import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.readermode.DefaultReaderModeController import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.toolbar.BrowserInteractor import org.mozilla.fenix.components.toolbar.BrowserToolbarController import org.mozilla.fenix.components.toolbar.BrowserToolbarViewInteractor import org.mozilla.fenix.components.toolbar.QuickActionSheetAction import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.increaseTapArea import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.sessioncontrol.SessionControlChange import org.mozilla.fenix.home.sessioncontrol.TabCollection import org.mozilla.fenix.mvi.getManagedEmitter import org.mozilla.fenix.quickactionsheet.DefaultQuickActionSheetController import org.mozilla.fenix.quickactionsheet.QuickActionSheetSessionObserver import org.mozilla.fenix.quickactionsheet.QuickActionSheetView /** * Fragment used for browsing the web within the main app. */ @ObsoleteCoroutinesApi @ExperimentalCoroutinesApi @Suppress("TooManyFunctions", "LargeClass") class BrowserFragment : BaseBrowserFragment(), BackHandler { private lateinit var quickActionSheetView: QuickActionSheetView private var quickActionSheetSessionObserver: QuickActionSheetSessionObserver? = null private val readerViewFeature = ViewBoundFeatureWrapper() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) postponeEnterTransition() sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move) .setDuration( SHARED_TRANSITION_MS ) } 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}" startPostponedEnterTransition() return view } override fun initializeUI(view: View): Session? { val context = requireContext() val sessionManager = context.components.core.sessionManager return super.initializeUI(view)?.also { readerViewFeature.set( feature = ReaderViewFeature( context, context.components.core.engine, sessionManager, view.readerViewControlsBar ) { available -> if (available) { context.components.analytics.metrics.track(Event.ReaderModeAvailable) } browserStore.apply { dispatch(QuickActionSheetAction.ReadableStateChange(available)) dispatch( QuickActionSheetAction.ReaderActiveStateChange( sessionManager.selectedSession?.readerMode ?: false ) ) } }, owner = this, view = view ) if ((activity as HomeActivity).browsingModeManager.mode.isPrivate) { // We need to update styles for private mode programmatically for now: // https://github.com/mozilla-mobile/android-components/issues/3400 themeReaderViewControlsForPrivateMode(view.readerViewControlsBar) } consumeFrom(browserStore) { quickActionSheetView.update(it) browserToolbarView.update(it) } } } override fun onStart() { super.onStart() subscribeToTabCollections() quickActionSheetSessionObserver = QuickActionSheetSessionObserver( lifecycleScope, requireComponents, dispatch = { action -> browserStore.dispatch(action) } ).also { observer -> getSessionById()?.register(observer, this, autoPause = true) } getSessionById()?.register(toolbarSessionObserver, this, autoPause = true) } private val toolbarSessionObserver = object : Session.Observer { override fun onLoadingStateChanged(session: Session, loading: Boolean) { if (!loading && shouldShowTrackingProtectionOnboarding(session) ) { showTrackingProtectionOnboarding() } } } override fun onResume() { super.onResume() getSessionById()?.let { /** * The session mode may be changed if the user is originally in Normal Mode and then * opens a 3rd party link in Private Browsing Mode. Hence, we update the theme here. * This fixes issue #5254. */ (activity as HomeActivity).updateThemeForSession(it) quickActionSheetSessionObserver?.updateBookmarkState(it) } requireComponents.core.tabCollectionStorage.register(collectionStorageObserver, this) } override fun onBackPressed(): Boolean { return readerViewFeature.onBackPressed() || super.onBackPressed() } override fun createBrowserToolbarViewInteractor( browserToolbarController: BrowserToolbarController, session: Session? ): BrowserToolbarViewInteractor { val context = requireContext() val interactor = BrowserInteractor( context = context, store = browserStore, browserToolbarController = browserToolbarController, quickActionSheetController = DefaultQuickActionSheetController( context = context, navController = findNavController(), currentSession = getSessionById() ?: context.components.core.sessionManager.selectedSessionOrThrow, appLinksUseCases = context.components.useCases.appLinksUseCases, bookmarkTapped = { lifecycleScope.launch { bookmarkTapped(it) } } ), readerModeController = DefaultReaderModeController(readerViewFeature), currentSession = session ) quickActionSheetView = QuickActionSheetView(view!!.nestedScrollQuickAction, interactor) return interactor } override fun navToQuickSettingsSheet(session: Session, sitePermissions: SitePermissions?) { val directions = BrowserFragmentDirections.actionBrowserFragmentToQuickSettingsSheetDialogFragment( sessionId = session.id, url = session.url, isSecured = session.securityInfo.secure, isTrackingProtectionOn = session.trackerBlockingEnabled, sitePermissions = sitePermissions, gravity = getAppropriateLayoutGravity() ) nav(R.id.browserFragment, directions) } override fun navToTrackingProtectionPanel(session: Session) { val directions = BrowserFragmentDirections.actionBrowserFragmentToTrackingProtectionPanelDialogFragment( sessionId = session.id, url = session.url, trackingProtectionEnabled = session.trackerBlockingEnabled, gravity = getAppropriateLayoutGravity() ) nav(R.id.browserFragment, directions) } override fun getEngineMargins(): Pair { val toolbarAndQASSize = resources.getDimensionPixelSize(R.dimen.toolbar_and_qab_height) return 0 to toolbarAndQASSize } override fun getAppropriateLayoutGravity() = Gravity.BOTTOM private fun themeReaderViewControlsForPrivateMode(view: View) = with(view) { listOf( R.id.mozac_feature_readerview_font_size_decrease, R.id.mozac_feature_readerview_font_size_increase ).map { findViewById