[fenix] Refactor ExternalAppBrowserActivity and ExternalAppBrowserFragment to not use Session(Manager).

pull/600/head
Sebastian Kaspari 3 years ago committed by Christian Sadilek
parent a1246d05f8
commit 086174a280

@ -184,6 +184,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
protected var webAppToolbarShouldBeVisible = true protected var webAppToolbarShouldBeVisible = true
private val sharedViewModel: SharedViewModel by activityViewModels() private val sharedViewModel: SharedViewModel by activityViewModels()
private val homeViewModel: HomeScreenViewModel by activityViewModels()
@VisibleForTesting @VisibleForTesting
internal val onboarding by lazy { FenixOnboarding(requireContext()) } internal val onboarding by lazy { FenixOnboarding(requireContext()) }
@ -220,7 +221,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
} }
final override fun onViewCreated(view: View, savedInstanceState: Bundle?) { final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
browserInitialized = initializeUI(view) != null initializeUI(view)
if (customTabSessionId == null) { if (customTabSessionId == null) {
// We currently only need this observer to navigate to home // We currently only need this observer to navigate to home
@ -238,12 +239,19 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
requireContext().accessibilityManager.addAccessibilityStateChangeListener(this) requireContext().accessibilityManager.addAccessibilityStateChangeListener(this)
} }
private val homeViewModel: HomeScreenViewModel by activityViewModels() private fun initializeUI(view: View) {
val tab = getCurrentTab()
browserInitialized = if (tab != null) {
initializeUI(view, tab)
true
} else {
false
}
}
@Suppress("ComplexMethod", "LongMethod") @Suppress("ComplexMethod", "LongMethod")
@CallSuper @CallSuper
@VisibleForTesting internal open fun initializeUI(view: View, tab: SessionState) {
internal open fun initializeUI(view: View): Session? {
val context = requireContext() val context = requireContext()
val sessionManager = context.components.core.sessionManager val sessionManager = context.components.core.sessionManager
val store = context.components.core.store val store = context.components.core.store
@ -260,456 +268,454 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
beginAnimateInIfNecessary() beginAnimateInIfNecessary()
} }
return getSessionById()?.also { _ -> val openInFenixIntent = Intent(context, IntentReceiverActivity::class.java).apply {
val openInFenixIntent = Intent(context, IntentReceiverActivity::class.java).apply { action = Intent.ACTION_VIEW
action = Intent.ACTION_VIEW putExtra(HomeActivity.OPEN_TO_BROWSER, true)
putExtra(HomeActivity.OPEN_TO_BROWSER, true) }
}
val readerMenuController = DefaultReaderModeController(
readerViewFeature,
view.readerViewControlsBar,
isPrivate = activity.browsingModeManager.mode.isPrivate
)
val browserToolbarController = DefaultBrowserToolbarController(
store = store,
activity = activity,
navController = findNavController(),
metrics = requireComponents.analytics.metrics,
readerModeController = readerMenuController,
sessionManager = requireComponents.core.sessionManager,
engineView = engineView,
homeViewModel = homeViewModel,
customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
onTabCounterClicked = {
thumbnailsFeature.get()?.requestScreenshot()
findNavController().nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalTabTrayDialogFragment()
)
},
onCloseTab = { closedSession ->
val tab = store.state.findTab(closedSession.id) ?: return@DefaultBrowserToolbarController
val snackbarMessage = if (tab.content.private) { val readerMenuController = DefaultReaderModeController(
requireContext().getString(R.string.snackbar_private_tab_closed) readerViewFeature,
} else { view.readerViewControlsBar,
requireContext().getString(R.string.snackbar_tab_closed) isPrivate = activity.browsingModeManager.mode.isPrivate
} )
val browserToolbarController = DefaultBrowserToolbarController(
store = store,
activity = activity,
navController = findNavController(),
metrics = requireComponents.analytics.metrics,
readerModeController = readerMenuController,
sessionManager = requireComponents.core.sessionManager,
engineView = engineView,
homeViewModel = homeViewModel,
customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
onTabCounterClicked = {
thumbnailsFeature.get()?.requestScreenshot()
findNavController().nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalTabTrayDialogFragment()
)
},
onCloseTab = { closedSession ->
val closedTab = store.state.findTab(closedSession.id) ?: return@DefaultBrowserToolbarController
val snackbarMessage = if (closedTab.content.private) {
requireContext().getString(R.string.snackbar_private_tab_closed)
} else {
requireContext().getString(R.string.snackbar_tab_closed)
}
viewLifecycleOwner.lifecycleScope.allowUndo( viewLifecycleOwner.lifecycleScope.allowUndo(
requireView().browserLayout, requireView().browserLayout,
snackbarMessage, snackbarMessage,
requireContext().getString(R.string.snackbar_deleted_undo), requireContext().getString(R.string.snackbar_deleted_undo),
{ {
requireComponents.useCases.tabsUseCases.undo.invoke() requireComponents.useCases.tabsUseCases.undo.invoke()
}, },
paddedForBottomToolbar = true, paddedForBottomToolbar = true,
operation = { } operation = { }
) )
}
)
val browserToolbarMenuController = DefaultBrowserToolbarMenuController(
activity = activity,
navController = findNavController(),
metrics = requireComponents.analytics.metrics,
settings = context.settings(),
readerModeController = readerMenuController,
sessionManager = requireComponents.core.sessionManager,
sessionFeature = sessionFeature,
findInPageLauncher = { findInPageIntegration.withFeature { it.launch() } },
swipeRefresh = swipeRefresh,
browserAnimator = browserAnimator,
customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
openInFenixIntent = openInFenixIntent,
bookmarkTapped = { url: String, title: String ->
viewLifecycleOwner.lifecycleScope.launch {
bookmarkTapped(url, title)
} }
) },
val browserToolbarMenuController = DefaultBrowserToolbarMenuController( scope = viewLifecycleOwner.lifecycleScope,
activity = activity, tabCollectionStorage = requireComponents.core.tabCollectionStorage,
navController = findNavController(), topSitesStorage = requireComponents.core.topSitesStorage,
metrics = requireComponents.analytics.metrics, browserStore = store
settings = context.settings(), )
readerModeController = readerMenuController,
sessionManager = requireComponents.core.sessionManager,
sessionFeature = sessionFeature,
findInPageLauncher = { findInPageIntegration.withFeature { it.launch() } },
swipeRefresh = swipeRefresh,
browserAnimator = browserAnimator,
customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
openInFenixIntent = openInFenixIntent,
bookmarkTapped = { url: String, title: String ->
viewLifecycleOwner.lifecycleScope.launch {
bookmarkTapped(url, title)
}
},
scope = viewLifecycleOwner.lifecycleScope,
tabCollectionStorage = requireComponents.core.tabCollectionStorage,
topSitesStorage = requireComponents.core.topSitesStorage,
browserStore = store
)
_browserInteractor = BrowserInteractor( _browserInteractor = BrowserInteractor(
browserToolbarController, browserToolbarController,
browserToolbarMenuController browserToolbarMenuController
) )
_browserToolbarView = BrowserToolbarView( _browserToolbarView = BrowserToolbarView(
container = view.browserLayout, container = view.browserLayout,
toolbarPosition = context.settings().toolbarPosition, toolbarPosition = context.settings().toolbarPosition,
interactor = browserInteractor, interactor = browserInteractor,
customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) }, customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
lifecycleOwner = viewLifecycleOwner lifecycleOwner = viewLifecycleOwner
) )
toolbarIntegration.set( toolbarIntegration.set(
feature = browserToolbarView.toolbarIntegration, feature = browserToolbarView.toolbarIntegration,
owner = this, owner = this,
view = view view = view
) )
findInPageIntegration.set( findInPageIntegration.set(
feature = FindInPageIntegration( feature = FindInPageIntegration(
store = store, store = store,
sessionId = customTabSessionId, sessionId = customTabSessionId,
stub = view.stubFindInPage, stub = view.stubFindInPage,
engineView = view.engineView, engineView = view.engineView,
toolbar = browserToolbarView.view toolbar = browserToolbarView.view
), ),
owner = this, owner = this,
view = view view = view
) )
browserToolbarView.view.display.setOnSiteSecurityClickedListener { browserToolbarView.view.display.setOnSiteSecurityClickedListener {
showQuickSettingsDialog() showQuickSettingsDialog()
} }
browserToolbarView.view.display.setOnTrackingProtectionClickedListener { browserToolbarView.view.display.setOnTrackingProtectionClickedListener {
context.metrics.track(Event.TrackingProtectionIconPressed) context.metrics.track(Event.TrackingProtectionIconPressed)
showTrackingProtectionPanel() showTrackingProtectionPanel()
} }
contextMenuFeature.set(
feature = ContextMenuFeature(
fragmentManager = parentFragmentManager,
store = store,
candidates = getContextMenuCandidates(context, view.browserLayout),
engineView = view.engineView,
useCases = context.components.useCases.contextMenuUseCases,
tabId = customTabSessionId
),
owner = this,
view = view
)
val allowScreenshotsInPrivateMode = context.settings().allowScreenshotsInPrivateMode
secureWindowFeature.set(
feature = SecureWindowFeature(
window = requireActivity().window,
store = store,
customTabId = customTabSessionId,
isSecure = { !allowScreenshotsInPrivateMode && it.content.private }
),
owner = this,
view = view
)
contextMenuFeature.set( if (newMediaSessionApi) {
feature = ContextMenuFeature( fullScreenMediaSessionFeature.set(
fragmentManager = parentFragmentManager, feature = MediaSessionFullscreenFeature(
store = store, requireActivity(),
candidates = getContextMenuCandidates(context, view.browserLayout), context.components.core.store
engineView = view.engineView,
useCases = context.components.useCases.contextMenuUseCases,
tabId = customTabSessionId
), ),
owner = this, owner = this,
view = view view = view
) )
} else {
val allowScreenshotsInPrivateMode = context.settings().allowScreenshotsInPrivateMode fullScreenMediaFeature.set(
secureWindowFeature.set( feature = MediaFullscreenOrientationFeature(
feature = SecureWindowFeature( requireActivity(),
window = requireActivity().window, context.components.core.store
store = store,
customTabId = customTabSessionId,
isSecure = { !allowScreenshotsInPrivateMode && it.content.private }
), ),
owner = this, owner = this,
view = view view = view
) )
}
if (newMediaSessionApi) { val downloadFeature = DownloadsFeature(
fullScreenMediaSessionFeature.set( context.applicationContext,
feature = MediaSessionFullscreenFeature( store = store,
requireActivity(), useCases = context.components.useCases.downloadUseCases,
context.components.core.store fragmentManager = childFragmentManager,
), tabId = customTabSessionId,
owner = this, downloadManager = FetchDownloadManager(
view = view
)
} else {
fullScreenMediaFeature.set(
feature = MediaFullscreenOrientationFeature(
requireActivity(),
context.components.core.store
),
owner = this,
view = view
)
}
val downloadFeature = DownloadsFeature(
context.applicationContext, context.applicationContext,
store = store, store,
useCases = context.components.useCases.downloadUseCases, DownloadService::class
fragmentManager = childFragmentManager, ),
tabId = customTabSessionId, shouldForwardToThirdParties = {
downloadManager = FetchDownloadManager( PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
context.applicationContext, context.getPreferenceKey(R.string.pref_key_external_download_manager), false
store, )
DownloadService::class },
promptsStyling = DownloadsFeature.PromptsStyling(
gravity = Gravity.BOTTOM,
shouldWidthMatchParent = true,
positiveButtonBackgroundColor = ThemeManager.resolveAttribute(
R.attr.accent,
context
), ),
shouldForwardToThirdParties = { positiveButtonTextColor = ThemeManager.resolveAttribute(
PreferenceManager.getDefaultSharedPreferences(context).getBoolean( R.attr.contrastText,
context.getPreferenceKey(R.string.pref_key_external_download_manager), false context
)
},
promptsStyling = DownloadsFeature.PromptsStyling(
gravity = Gravity.BOTTOM,
shouldWidthMatchParent = true,
positiveButtonBackgroundColor = ThemeManager.resolveAttribute(
R.attr.accent,
context
),
positiveButtonTextColor = ThemeManager.resolveAttribute(
R.attr.contrastText,
context
),
positiveButtonRadius = (resources.getDimensionPixelSize(R.dimen.tab_corner_radius)).toFloat()
), ),
onNeedToRequestPermissions = { permissions -> positiveButtonRadius = (resources.getDimensionPixelSize(R.dimen.tab_corner_radius)).toFloat()
requestPermissions(permissions, REQUEST_CODE_DOWNLOAD_PERMISSIONS) ),
} onNeedToRequestPermissions = { permissions ->
) requestPermissions(permissions, REQUEST_CODE_DOWNLOAD_PERMISSIONS)
}
)
downloadFeature.onDownloadStopped = { downloadState, _, downloadJobStatus -> downloadFeature.onDownloadStopped = { downloadState, _, downloadJobStatus ->
// If the download is just paused, don't show any in-app notification // If the download is just paused, don't show any in-app notification
if (downloadJobStatus == DownloadState.Status.COMPLETED || if (downloadJobStatus == DownloadState.Status.COMPLETED ||
downloadJobStatus == DownloadState.Status.FAILED downloadJobStatus == DownloadState.Status.FAILED
) { ) {
saveDownloadDialogState( saveDownloadDialogState(
downloadState.sessionId, downloadState.sessionId,
downloadState, downloadState,
downloadJobStatus downloadJobStatus
) )
val dynamicDownloadDialog = DynamicDownloadDialog( val dynamicDownloadDialog = DynamicDownloadDialog(
container = view.browserLayout, container = view.browserLayout,
downloadState = downloadState, downloadState = downloadState,
metrics = requireComponents.analytics.metrics, metrics = requireComponents.analytics.metrics,
didFail = downloadJobStatus == DownloadState.Status.FAILED, didFail = downloadJobStatus == DownloadState.Status.FAILED,
tryAgain = downloadFeature::tryAgain, tryAgain = downloadFeature::tryAgain,
onCannotOpenFile = { onCannotOpenFile = {
FenixSnackbar.make( FenixSnackbar.make(
view = view.browserLayout, view = view.browserLayout,
duration = Snackbar.LENGTH_SHORT, duration = Snackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = true isDisplayedWithBrowserToolbar = true
) )
.setText(context.getString(R.string.mozac_feature_downloads_could_not_open_file)) .setText(context.getString(R.string.mozac_feature_downloads_could_not_open_file))
.show() .show()
}, },
view = view.viewDynamicDownloadDialog, view = view.viewDynamicDownloadDialog,
toolbarHeight = toolbarHeight, toolbarHeight = toolbarHeight,
onDismiss = { sharedViewModel.downloadDialogState.remove(downloadState.sessionId) } onDismiss = { sharedViewModel.downloadDialogState.remove(downloadState.sessionId) }
) )
// Don't show the dialog if we aren't in the tab that started the download // Don't show the dialog if we aren't in the tab that started the download
if (downloadState.sessionId == sessionManager.selectedSession?.id) { if (downloadState.sessionId == sessionManager.selectedSession?.id) {
dynamicDownloadDialog.show() dynamicDownloadDialog.show()
browserToolbarView.expand() browserToolbarView.expand()
}
} }
} }
}
resumeDownloadDialogState( resumeDownloadDialogState(
sessionManager.selectedSession?.id, sessionManager.selectedSession?.id,
store, view, context, toolbarHeight store, view, context, toolbarHeight
) )
downloadsFeature.set( downloadsFeature.set(
downloadFeature, downloadFeature,
owner = this, owner = this,
view = view view = view
) )
pipFeature = PictureInPictureFeature( pipFeature = PictureInPictureFeature(
store = store,
activity = requireActivity(),
crashReporting = context.components.analytics.crashReporter,
tabId = customTabSessionId
)
appLinksFeature.set(
feature = AppLinksFeature(
context,
store = store, store = store,
activity = requireActivity(), sessionId = customTabSessionId,
crashReporting = context.components.analytics.crashReporter, fragmentManager = parentFragmentManager,
tabId = customTabSessionId launchInApp = { context.settings().openLinksInExternalApp },
) loadUrlUseCase = context.components.useCases.sessionUseCases.loadUrl
),
owner = this,
view = view
)
appLinksFeature.set( promptsFeature.set(
feature = AppLinksFeature( feature = PromptFeature(
context, fragment = this,
store = store, store = store,
sessionId = customTabSessionId, customTabId = customTabSessionId,
fragmentManager = parentFragmentManager, fragmentManager = parentFragmentManager,
launchInApp = { context.settings().openLinksInExternalApp }, loginValidationDelegate = DefaultLoginValidationDelegate(
loadUrlUseCase = context.components.useCases.sessionUseCases.loadUrl context.components.core.lazyPasswordsStorage
), ),
owner = this, isSaveLoginEnabled = {
view = view context.settings().shouldPromptToSaveLogins
) },
loginExceptionStorage = context.components.core.loginExceptionStorage,
promptsFeature.set( shareDelegate = object : ShareDelegate {
feature = PromptFeature( override fun showShareSheet(
fragment = this, context: Context,
store = store, shareData: ShareData,
customTabId = customTabSessionId, onDismiss: () -> Unit,
fragmentManager = parentFragmentManager, onSuccess: () -> Unit
loginValidationDelegate = DefaultLoginValidationDelegate( ) {
context.components.core.lazyPasswordsStorage val directions = NavGraphDirections.actionGlobalShareFragment(
), data = arrayOf(shareData),
isSaveLoginEnabled = { showPage = true,
context.settings().shouldPromptToSaveLogins sessionId = getSessionById()?.id
}, )
loginExceptionStorage = context.components.core.loginExceptionStorage, findNavController().navigate(directions)
shareDelegate = object : ShareDelegate {
override fun showShareSheet(
context: Context,
shareData: ShareData,
onDismiss: () -> Unit,
onSuccess: () -> Unit
) {
val directions = NavGraphDirections.actionGlobalShareFragment(
data = arrayOf(shareData),
showPage = true,
sessionId = getSessionById()?.id
)
findNavController().navigate(directions)
}
},
onNeedToRequestPermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_PROMPT_PERMISSIONS)
},
loginPickerView = loginSelectBar,
onManageLogins = {
browserAnimator.captureEngineViewAndDrawStatically {
val directions =
NavGraphDirections.actionGlobalSavedLoginsAuthFragment()
findNavController().navigate(directions)
}
} }
), },
owner = this, onNeedToRequestPermissions = { permissions ->
view = view requestPermissions(permissions, REQUEST_CODE_PROMPT_PERMISSIONS)
) },
loginPickerView = loginSelectBar,
onManageLogins = {
browserAnimator.captureEngineViewAndDrawStatically {
val directions =
NavGraphDirections.actionGlobalSavedLoginsAuthFragment()
findNavController().navigate(directions)
}
}
),
owner = this,
view = view
)
sessionFeature.set( sessionFeature.set(
feature = SessionFeature( feature = SessionFeature(
requireComponents.core.store, requireComponents.core.store,
requireComponents.useCases.sessionUseCases.goBack, requireComponents.useCases.sessionUseCases.goBack,
view.engineView, view.engineView,
customTabSessionId customTabSessionId
), ),
owner = this, owner = this,
view = view view = view
) )
searchFeature.set( searchFeature.set(
feature = SearchFeature(store, customTabSessionId) { request, tabId -> feature = SearchFeature(store, customTabSessionId) { request, tabId ->
val parentSession = sessionManager.findSessionById(tabId) val parentSession = sessionManager.findSessionById(tabId)
val useCase = if (request.isPrivate) { val useCase = if (request.isPrivate) {
requireComponents.useCases.searchUseCases.newPrivateTabSearch requireComponents.useCases.searchUseCases.newPrivateTabSearch
} else { } else {
requireComponents.useCases.searchUseCases.newTabSearch requireComponents.useCases.searchUseCases.newTabSearch
} }
if (parentSession?.isCustomTabSession() == true) { if (parentSession?.isCustomTabSession() == true) {
useCase.invoke(request.query) useCase.invoke(request.query)
requireActivity().startActivity(openInFenixIntent) requireActivity().startActivity(openInFenixIntent)
} else { } else {
useCase.invoke(request.query, parentSessionId = parentSession?.id) useCase.invoke(request.query, parentSessionId = parentSession?.id)
} }
}, },
owner = this, owner = this,
view = view view = view
) )
val accentHighContrastColor = val accentHighContrastColor =
ThemeManager.resolveAttribute(R.attr.accentHighContrast, context) ThemeManager.resolveAttribute(R.attr.accentHighContrast, context)
sitePermissionsFeature.set(
feature = SitePermissionsFeature(
context = context,
storage = context.components.core.permissionStorage.permissionsStorage,
fragmentManager = parentFragmentManager,
promptsStyling = SitePermissionsFeature.PromptsStyling(
gravity = getAppropriateLayoutGravity(),
shouldWidthMatchParent = true,
positiveButtonBackgroundColor = accentHighContrastColor,
positiveButtonTextColor = R.color.photonWhite
),
sessionId = customTabSessionId,
onNeedToRequestPermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_APP_PERMISSIONS)
},
onShouldShowRequestPermissionRationale = {
shouldShowRequestPermissionRationale(
it
)
},
store = store
),
owner = this,
view = view
)
sitePermissionWifiIntegration.set( sitePermissionsFeature.set(
feature = SitePermissionsWifiIntegration( feature = SitePermissionsFeature(
settings = context.settings(), context = context,
wifiConnectionMonitor = context.components.wifiConnectionMonitor storage = context.components.core.permissionStorage.permissionsStorage,
fragmentManager = parentFragmentManager,
promptsStyling = SitePermissionsFeature.PromptsStyling(
gravity = getAppropriateLayoutGravity(),
shouldWidthMatchParent = true,
positiveButtonBackgroundColor = accentHighContrastColor,
positiveButtonTextColor = R.color.photonWhite
), ),
owner = this, sessionId = customTabSessionId,
view = view onNeedToRequestPermissions = { permissions ->
) requestPermissions(permissions, REQUEST_CODE_APP_PERMISSIONS)
},
if (FeatureFlags.webAuthFeature) { onShouldShowRequestPermissionRationale = {
webAuthnFeature.set( shouldShowRequestPermissionRationale(
feature = WebAuthnFeature( it
engine = requireComponents.core.engine, )
activity = requireActivity() },
), store = store
owner = this, ),
view = view owner = this,
) view = view
} )
context.settings().setSitePermissionSettingListener(viewLifecycleOwner) { sitePermissionWifiIntegration.set(
// If the user connects to WIFI while on the BrowserFragment, this will update the feature = SitePermissionsWifiIntegration(
// SitePermissionsRules (specifically autoplay) accordingly settings = context.settings(),
runIfFragmentIsAttached { wifiConnectionMonitor = context.components.wifiConnectionMonitor
assignSitePermissionsRules() ),
} owner = this,
} view = view
assignSitePermissionsRules() )
fullScreenFeature.set( if (FeatureFlags.webAuthFeature) {
feature = FullScreenFeature( webAuthnFeature.set(
requireComponents.core.store, feature = WebAuthnFeature(
requireComponents.useCases.sessionUseCases, engine = requireComponents.core.engine,
customTabSessionId, activity = requireActivity()
::viewportFitChange,
::fullScreenChanged
), ),
owner = this, owner = this,
view = view view = view
) )
}
expandToolbarOnNavigation(store) context.settings().setSitePermissionSettingListener(viewLifecycleOwner) {
// If the user connects to WIFI while on the BrowserFragment, this will update the
store.flowScoped(viewLifecycleOwner) { flow -> // SitePermissionsRules (specifically autoplay) accordingly
flow.mapNotNull { state -> state.findTabOrCustomTabOrSelectedTab(customTabSessionId) } runIfFragmentIsAttached {
.ifChanged { tab -> tab.content.pictureInPictureEnabled } assignSitePermissionsRules()
.collect { tab -> pipModeChanged(tab) }
} }
}
assignSitePermissionsRules()
fullScreenFeature.set(
feature = FullScreenFeature(
requireComponents.core.store,
requireComponents.useCases.sessionUseCases,
customTabSessionId,
::viewportFitChange,
::fullScreenChanged
),
owner = this,
view = view
)
view.swipeRefresh.isEnabled = shouldPullToRefreshBeEnabled(false) expandToolbarOnNavigation(store)
if (view.swipeRefresh.isEnabled) {
val primaryTextColor =
ThemeManager.resolveAttribute(R.attr.primaryText, context)
view.swipeRefresh.setColorSchemeColors(primaryTextColor)
swipeRefreshFeature.set(
feature = SwipeRefreshFeature(
requireComponents.core.store,
context.components.useCases.sessionUseCases.reload,
view.swipeRefresh,
customTabSessionId
),
owner = this,
view = view
)
}
webchannelIntegration.set( store.flowScoped(viewLifecycleOwner) { flow ->
feature = FxaWebChannelFeature( flow.mapNotNull { state -> state.findTabOrCustomTabOrSelectedTab(customTabSessionId) }
requireContext(), .ifChanged { tab -> tab.content.pictureInPictureEnabled }
customTabSessionId, .collect { tab -> pipModeChanged(tab) }
requireComponents.core.engine, }
view.swipeRefresh.isEnabled = shouldPullToRefreshBeEnabled(false)
if (view.swipeRefresh.isEnabled) {
val primaryTextColor =
ThemeManager.resolveAttribute(R.attr.primaryText, context)
view.swipeRefresh.setColorSchemeColors(primaryTextColor)
swipeRefreshFeature.set(
feature = SwipeRefreshFeature(
requireComponents.core.store, requireComponents.core.store,
requireComponents.backgroundServices.accountManager, context.components.useCases.sessionUseCases.reload,
requireComponents.backgroundServices.serverConfig, view.swipeRefresh,
setOf(FxaCapability.CHOOSE_WHAT_TO_SYNC) customTabSessionId
), ),
owner = this, owner = this,
view = view view = view
) )
initializeEngineView(toolbarHeight)
} }
webchannelIntegration.set(
feature = FxaWebChannelFeature(
requireContext(),
customTabSessionId,
requireComponents.core.engine,
requireComponents.core.store,
requireComponents.backgroundServices.accountManager,
requireComponents.backgroundServices.serverConfig,
setOf(FxaCapability.CHOOSE_WHAT_TO_SYNC)
),
owner = this,
view = view
)
initializeEngineView(toolbarHeight)
} }
@VisibleForTesting @VisibleForTesting
@ -928,9 +934,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
resumeDownloadDialogState(selectedTab.id, context.components.core.store, view, context, toolbarHeight) resumeDownloadDialogState(selectedTab.id, context.components.core.store, view, context, toolbarHeight)
} }
} else { } else {
view?.let { view -> view?.let { view -> initializeUI(view) }
browserInitialized = initializeUI(view) != null
}
} }
} }
@ -1070,7 +1074,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
sitePermissions: SitePermissions? sitePermissions: SitePermissions?
) )
protected abstract fun navToTrackingProtectionPanel(session: Session) protected abstract fun navToTrackingProtectionPanel(tab: SessionState)
/** /**
* Returns the layout [android.view.Gravity] for the quick settings and ETP dialog. * Returns the layout [android.view.Gravity] for the quick settings and ETP dialog.
@ -1108,9 +1112,9 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
} }
private fun showTrackingProtectionPanel() { private fun showTrackingProtectionPanel() {
val session = getSessionById() ?: return val tab = getCurrentTab() ?: return
view?.let { view?.let {
navToTrackingProtectionPanel(session) navToTrackingProtectionPanel(tab)
} }
} }

@ -15,7 +15,6 @@ import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_browser.* import kotlinx.android.synthetic.main.fragment_browser.*
import kotlinx.android.synthetic.main.fragment_browser.view.* import kotlinx.android.synthetic.main.fragment_browser.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.session.Session
import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.SessionState
@ -56,107 +55,107 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
private var pwaOnboardingObserver: PwaOnboardingObserver? = null private var pwaOnboardingObserver: PwaOnboardingObserver? = null
@Suppress("LongMethod") @Suppress("LongMethod")
override fun initializeUI(view: View): Session? { override fun initializeUI(view: View, tab: SessionState) {
super.initializeUI(view, tab)
val context = requireContext() val context = requireContext()
val components = context.components val components = context.components
return super.initializeUI(view)?.also { if (context.settings().isSwipeToolbarToSwitchTabsEnabled) {
if (context.settings().isSwipeToolbarToSwitchTabsEnabled) { gestureLayout.addGestureListener(
gestureLayout.addGestureListener( ToolbarGestureHandler(
ToolbarGestureHandler( activity = requireActivity(),
activity = requireActivity(), contentLayout = browserLayout,
contentLayout = browserLayout, tabPreview = tabPreview,
tabPreview = tabPreview, toolbarLayout = browserToolbarView.view,
toolbarLayout = browserToolbarView.view, store = components.core.store,
store = components.core.store, sessionManager = components.core.sessionManager
sessionManager = components.core.sessionManager
)
)
}
val readerModeAction =
BrowserToolbar.ToggleButton(
image = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_readermode)!!,
imageSelected =
AppCompatResources.getDrawable(requireContext(), R.drawable.ic_readermode_selected)!!,
contentDescription = requireContext().getString(R.string.browser_menu_read),
contentDescriptionSelected = requireContext().getString(R.string.browser_menu_read_close),
visible = {
readerModeAvailable
},
selected = getSessionById()?.let {
activity?.components?.core?.store?.state?.findTab(it.id)?.readerState?.active
} ?: false,
listener = browserInteractor::onReaderModePressed
) )
)
}
browserToolbarView.view.addPageAction(readerModeAction) val readerModeAction =
BrowserToolbar.ToggleButton(
thumbnailsFeature.set( image = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_readermode)!!,
feature = BrowserThumbnails(context, view.engineView, components.core.store), imageSelected =
owner = this, AppCompatResources.getDrawable(requireContext(), R.drawable.ic_readermode_selected)!!,
view = view contentDescription = requireContext().getString(R.string.browser_menu_read),
contentDescriptionSelected = requireContext().getString(R.string.browser_menu_read_close),
visible = {
readerModeAvailable
},
selected = getSessionById()?.let {
activity?.components?.core?.store?.state?.findTab(it.id)?.readerState?.active
} ?: false,
listener = browserInteractor::onReaderModePressed
) )
readerViewFeature.set( browserToolbarView.view.addPageAction(readerModeAction)
feature = components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
ReaderViewFeature(
context,
components.core.engine,
components.core.store,
view.readerViewControlsBar
) { available, active ->
if (available) {
components.analytics.metrics.track(Event.ReaderModeAvailable)
}
readerModeAvailable = available thumbnailsFeature.set(
readerModeAction.setSelected(active) feature = BrowserThumbnails(context, view.engineView, components.core.store),
safeInvalidateBrowserToolbarView() owner = this,
view = view
)
readerViewFeature.set(
feature = components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
ReaderViewFeature(
context,
components.core.engine,
components.core.store,
view.readerViewControlsBar
) { available, active ->
if (available) {
components.analytics.metrics.track(Event.ReaderModeAvailable)
} }
},
readerModeAvailable = available
readerModeAction.setSelected(active)
safeInvalidateBrowserToolbarView()
}
},
owner = this,
view = view
)
windowFeature.set(
feature = WindowFeature(
store = components.core.store,
tabsUseCases = components.useCases.tabsUseCases
),
owner = this,
view = view
)
if (context.settings().shouldShowOpenInAppCfr) {
openInAppOnboardingObserver.set(
feature = OpenInAppOnboardingObserver(
context = context,
store = context.components.core.store,
lifecycleOwner = this,
navController = findNavController(),
settings = context.settings(),
appLinksUseCases = context.components.useCases.appLinksUseCases,
container = browserLayout as ViewGroup
),
owner = this, owner = this,
view = view view = view
) )
}
windowFeature.set( if (context.settings().shouldShowTrackingProtectionCfr) {
feature = WindowFeature( trackingProtectionOverlayObserver.set(
store = components.core.store, feature = TrackingProtectionOverlay(
tabsUseCases = components.useCases.tabsUseCases context = context,
store = context.components.core.store,
lifecycleOwner = viewLifecycleOwner,
settings = context.settings(),
metrics = context.components.analytics.metrics,
getToolbar = { browserToolbarView.view }
), ),
owner = this, owner = this,
view = view view = view
) )
if (context.settings().shouldShowOpenInAppCfr) {
openInAppOnboardingObserver.set(
feature = OpenInAppOnboardingObserver(
context = context,
store = context.components.core.store,
lifecycleOwner = this,
navController = findNavController(),
settings = context.settings(),
appLinksUseCases = context.components.useCases.appLinksUseCases,
container = browserLayout as ViewGroup
),
owner = this,
view = view
)
}
if (context.settings().shouldShowTrackingProtectionCfr) {
trackingProtectionOverlayObserver.set(
feature = TrackingProtectionOverlay(
context = context,
store = context.components.core.store,
lifecycleOwner = viewLifecycleOwner,
settings = context.settings(),
metrics = context.components.analytics.metrics,
getToolbar = { browserToolbarView.view }
),
owner = this,
view = view
)
}
} }
} }
@ -219,15 +218,15 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
nav(R.id.browserFragment, directions) nav(R.id.browserFragment, directions)
} }
override fun navToTrackingProtectionPanel(session: Session) { override fun navToTrackingProtectionPanel(tab: SessionState) {
val navController = findNavController() val navController = findNavController()
requireComponents.useCases.trackingProtectionUseCases.containsException(session.id) { contains -> requireComponents.useCases.trackingProtectionUseCases.containsException(tab.id) { contains ->
val isEnabled = session.trackerBlockingEnabled && !contains val isEnabled = tab.trackingProtection.enabled && !contains
val directions = val directions =
BrowserFragmentDirections.actionBrowserFragmentToTrackingProtectionPanelDialogFragment( BrowserFragmentDirections.actionBrowserFragmentToTrackingProtectionPanelDialogFragment(
sessionId = session.id, sessionId = tab.id,
url = session.url, url = tab.content.url,
trackingProtectionEnabled = isEnabled, trackingProtectionEnabled = isEnabled,
gravity = getAppropriateLayoutGravity() gravity = getAppropriateLayoutGravity()
) )

@ -9,7 +9,6 @@ import androidx.annotation.VisibleForTesting
import androidx.navigation.NavDestination import androidx.navigation.NavDestination
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.activity_home.*
import mozilla.components.browser.session.runWithSession
import mozilla.components.browser.state.selector.findCustomTab import mozilla.components.browser.state.selector.findCustomTab
import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.SessionState
import mozilla.components.concept.engine.manifest.WebAppManifestParser import mozilla.components.concept.engine.manifest.WebAppManifestParser
@ -102,12 +101,9 @@ open class ExternalAppBrowserActivity : HomeActivity() {
// When this activity finishes, the process is staying around and the session still // When this activity finishes, the process is staying around and the session still
// exists then remove it now to free all its resources. Once this activity is finished // exists then remove it now to free all its resources. Once this activity is finished
// then there's no way to get back to it other than relaunching it. // then there's no way to get back to it other than relaunching it.
components.core.sessionManager.runWithSession(getExternalTabId()) { session -> val tabId = getExternalTabId()
// If the custom tag config has been removed we are opening this in normal browsing if (tabId != null) {
if (session.customTabConfig != null) { components.useCases.customTabsUseCases.remove(tabId)
remove(session)
}
true
} }
} }
} }

@ -14,7 +14,6 @@ import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.component_browser_top_toolbar.* import kotlinx.android.synthetic.main.component_browser_top_toolbar.*
import kotlinx.android.synthetic.main.fragment_browser.* import kotlinx.android.synthetic.main.fragment_browser.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.session.Session
import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.SessionState
import mozilla.components.concept.engine.manifest.WebAppManifestParser import mozilla.components.concept.engine.manifest.WebAppManifestParser
import mozilla.components.concept.engine.manifest.getOrNull import mozilla.components.concept.engine.manifest.getOrNull
@ -52,113 +51,109 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
private val hideToolbarFeature = ViewBoundFeatureWrapper<WebAppHideToolbarFeature>() private val hideToolbarFeature = ViewBoundFeatureWrapper<WebAppHideToolbarFeature>()
@Suppress("LongMethod", "ComplexMethod") @Suppress("LongMethod", "ComplexMethod")
override fun initializeUI(view: View): Session? { override fun initializeUI(view: View, tab: SessionState) {
return super.initializeUI(view)?.also { super.initializeUI(view, tab)
val activity = requireActivity()
val components = activity.components val customTabSessionId = customTabSessionId ?: return
val activity = requireActivity()
val manifest = args.webAppManifest?.let { json -> val components = activity.components
WebAppManifestParser().parse(json).getOrNull() val manifest = args.webAppManifest?.let { json -> WebAppManifestParser().parse(json).getOrNull() }
}
customTabsIntegration.set(
customTabSessionId?.let { customTabSessionId -> feature = CustomTabsIntegration(
customTabsIntegration.set( sessionManager = requireComponents.core.sessionManager,
feature = CustomTabsIntegration( store = requireComponents.core.store,
sessionManager = requireComponents.core.sessionManager, useCases = requireComponents.useCases.customTabsUseCases,
store = requireComponents.core.store, toolbar = toolbar,
useCases = requireComponents.useCases.customTabsUseCases, sessionId = customTabSessionId,
toolbar = toolbar, activity = activity,
sessionId = customTabSessionId, onItemTapped = { browserInteractor.onBrowserToolbarMenuItemTapped(it) },
activity = activity, isPrivate = tab.content.private,
onItemTapped = { browserInteractor.onBrowserToolbarMenuItemTapped(it) }, shouldReverseItems = !activity.settings().shouldUseBottomToolbar
isPrivate = it.private, ),
shouldReverseItems = !activity.settings().shouldUseBottomToolbar owner = this,
), view = view
owner = this, )
view = view
)
windowFeature.set( windowFeature.set(
feature = CustomTabWindowFeature( feature = CustomTabWindowFeature(
activity, activity,
components.core.store, components.core.store,
customTabSessionId customTabSessionId
) { uri -> ) { uri ->
val intent = val intent =
Intent.parseUri("${BuildConfig.DEEP_LINK_SCHEME}://open?url=$uri", 0) Intent.parseUri("${BuildConfig.DEEP_LINK_SCHEME}://open?url=$uri", 0)
if (intent.action == Intent.ACTION_VIEW) { if (intent.action == Intent.ACTION_VIEW) {
intent.addCategory(Intent.CATEGORY_BROWSABLE) intent.addCategory(Intent.CATEGORY_BROWSABLE)
intent.component = null intent.component = null
intent.selector = null intent.selector = null
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
} }
activity.startActivity(intent) activity.startActivity(intent)
}, },
owner = this, owner = this,
view = view view = view
) )
hideToolbarFeature.set( hideToolbarFeature.set(
feature = WebAppHideToolbarFeature( feature = WebAppHideToolbarFeature(
store = requireComponents.core.store, store = requireComponents.core.store,
customTabsStore = requireComponents.core.customTabsStore, customTabsStore = requireComponents.core.customTabsStore,
tabId = customTabSessionId, tabId = customTabSessionId,
manifest = manifest manifest = manifest
) { toolbarVisible -> ) { toolbarVisible ->
browserToolbarView.view.isVisible = toolbarVisible browserToolbarView.view.isVisible = toolbarVisible
webAppToolbarShouldBeVisible = toolbarVisible webAppToolbarShouldBeVisible = toolbarVisible
if (!toolbarVisible) { if (!toolbarVisible) {
engineView.setDynamicToolbarMaxHeight(0) engineView.setDynamicToolbarMaxHeight(0)
val browserEngine = val browserEngine =
swipeRefresh.layoutParams as CoordinatorLayout.LayoutParams swipeRefresh.layoutParams as CoordinatorLayout.LayoutParams
browserEngine.bottomMargin = 0 browserEngine.bottomMargin = 0
} }
}, },
owner = this, owner = this,
view = toolbar view = toolbar
) )
if (manifest != null) { if (manifest != null) {
activity.lifecycle.addObservers( activity.lifecycle.addObservers(
WebAppActivityFeature( WebAppActivityFeature(
activity, activity,
components.core.icons, components.core.icons,
manifest manifest
), ),
ManifestUpdateFeature( ManifestUpdateFeature(
activity.applicationContext, activity.applicationContext,
requireComponents.core.store, requireComponents.core.store,
requireComponents.core.webAppShortcutManager, requireComponents.core.webAppShortcutManager,
requireComponents.core.webAppManifestStorage, requireComponents.core.webAppManifestStorage,
customTabSessionId, customTabSessionId,
manifest manifest
) )
) )
viewLifecycleOwner.lifecycle.addObserver( viewLifecycleOwner.lifecycle.addObserver(
WebAppSiteControlsFeature( WebAppSiteControlsFeature(
activity.applicationContext, activity.applicationContext,
requireComponents.core.store, requireComponents.core.store,
requireComponents.useCases.sessionUseCases.reload, requireComponents.useCases.sessionUseCases.reload,
customTabSessionId, customTabSessionId,
manifest, manifest,
WebAppSiteControlsBuilder( WebAppSiteControlsBuilder(
requireComponents.core.sessionManager, requireComponents.core.sessionManager,
requireComponents.useCases.sessionUseCases.reload, requireComponents.useCases.sessionUseCases.reload,
customTabSessionId, customTabSessionId,
manifest manifest
)
)
)
} else {
viewLifecycleOwner.lifecycle.addObserver(
PoweredByNotification(
activity.applicationContext,
requireComponents.core.store,
customTabSessionId
)
) )
} )
} )
} else {
viewLifecycleOwner.lifecycle.addObserver(
PoweredByNotification(
activity.applicationContext,
requireComponents.core.store,
customTabSessionId
)
)
} }
} }
@ -197,14 +192,14 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
nav(R.id.externalAppBrowserFragment, directions) nav(R.id.externalAppBrowserFragment, directions)
} }
override fun navToTrackingProtectionPanel(session: Session) { override fun navToTrackingProtectionPanel(tab: SessionState) {
requireComponents.useCases.trackingProtectionUseCases.containsException(session.id) { contains -> requireComponents.useCases.trackingProtectionUseCases.containsException(tab.id) { contains ->
val isEnabled = session.trackerBlockingEnabled && !contains val isEnabled = tab.trackingProtection.enabled && !contains
val directions = val directions =
ExternalAppBrowserFragmentDirections ExternalAppBrowserFragmentDirections
.actionGlobalTrackingProtectionPanelDialogFragment( .actionGlobalTrackingProtectionPanelDialogFragment(
sessionId = session.id, sessionId = tab.id,
url = session.url, url = tab.content.url,
trackingProtectionEnabled = isEnabled, trackingProtectionEnabled = isEnabled,
gravity = getAppropriateLayoutGravity() gravity = getAppropriateLayoutGravity()
) )

@ -87,13 +87,13 @@ class BrowserFragmentTest {
every { browserFragment.onboarding } returns onboarding every { browserFragment.onboarding } returns onboarding
every { browserFragment.requireContext() } returns context every { browserFragment.requireContext() } returns context
every { browserFragment.initializeUI(any()) } returns mockk() every { browserFragment.initializeUI(any(), any()) } returns mockk()
every { browserFragment.fullScreenChanged(any()) } returns Unit every { browserFragment.fullScreenChanged(any()) } returns Unit
every { browserFragment.resumeDownloadDialogState(any(), any(), any(), any(), any()) } returns Unit every { browserFragment.resumeDownloadDialogState(any(), any(), any(), any(), any()) } returns Unit
testTab = createTab(url = "https://mozilla.org")
store = BrowserStore() store = BrowserStore()
every { context.components.core.store } returns store every { context.components.core.store } returns store
testTab = createTab(url = "https://mozilla.org")
} }
@After @After
@ -122,10 +122,10 @@ class BrowserFragmentTest {
@Test @Test
fun `GIVEN browser UI is not initialized WHEN selected tab changes THEN browser UI is initialized`() { fun `GIVEN browser UI is not initialized WHEN selected tab changes THEN browser UI is initialized`() {
browserFragment.observeTabSelection(store) browserFragment.observeTabSelection(store)
verify(exactly = 0) { browserFragment.initializeUI(view) } verify(exactly = 0) { browserFragment.initializeUI(view, testTab) }
addAndSelectTab(testTab) addAndSelectTab(testTab)
verify(exactly = 1) { browserFragment.initializeUI(view) } verify(exactly = 1) { browserFragment.initializeUI(view, testTab) }
} }
@Test @Test

Loading…
Cancel
Save