diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt index 352f44a44..e4cabd939 100644 --- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt +++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt @@ -14,11 +14,6 @@ object FeatureFlags { */ const val pullToRefreshEnabled = false - /** - * Allows edit of saved logins. - */ - const val loginsEdit = true - /** * Shows Synced Tabs in the tabs tray. * diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index 246cf671b..fc5552856 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -159,15 +159,15 @@ open class FenixApplication : LocaleAwareApplication(), Provider { } private fun initVisualCompletenessQueueAndQueueTasks() { - val taskQueue = components.performance.visualCompletenessQueue + val queue = components.performance.visualCompletenessQueue.queue fun initQueue() { - registerActivityLifecycleCallbacks(PerformanceActivityLifecycleCallbacks(taskQueue)) + registerActivityLifecycleCallbacks(PerformanceActivityLifecycleCallbacks(queue)) } fun queueInitExperiments() { if (settings().isExperimentationEnabled) { - taskQueue.runIfReadyOrQueue { + queue.runIfReadyOrQueue { Experiments.initialize( applicationContext = applicationContext, onExperimentsUpdated = { @@ -188,7 +188,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { } fun queueInitStorageAndServices() { - components.performance.visualCompletenessQueue.runIfReadyOrQueue { + components.performance.visualCompletenessQueue.queue.runIfReadyOrQueue { GlobalScope.launch(Dispatchers.IO) { logger.info("Running post-visual completeness tasks...") logElapsedTime(logger, "Storage initialization") { @@ -208,7 +208,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { fun queueMetrics() { if (SDK_INT >= Build.VERSION_CODES.O) { // required by StorageStatsMetrics. - taskQueue.runIfReadyOrQueue { + queue.runIfReadyOrQueue { // Because it may be slow to capture the storage stats, it might be preferred to // create a WorkManager task for this metric, however, I ran out of // implementation time and WorkManager is harder to test. diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 86f26ed9e..c870dd394 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -21,7 +21,6 @@ import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting.PROTECTED import androidx.appcompat.app.ActionBar import androidx.appcompat.widget.Toolbar -import androidx.core.view.doOnPreDraw import androidx.lifecycle.lifecycleScope import androidx.navigation.NavDestination import androidx.navigation.NavDirections @@ -56,7 +55,6 @@ import mozilla.components.support.ktx.android.content.share import mozilla.components.support.ktx.kotlin.isUrl import mozilla.components.support.ktx.kotlin.toNormalizedUrl import mozilla.components.support.locale.LocaleAwareAppCompatActivity -import mozilla.components.support.utils.RunWhenReadyQueue import mozilla.components.support.utils.SafeIntent import mozilla.components.support.utils.toSafeIntent import mozilla.components.support.webextensions.WebExtensionPopupFeature @@ -104,6 +102,7 @@ import org.mozilla.fenix.tabtray.TabTrayDialogFragmentDirections import org.mozilla.fenix.theme.DefaultThemeManager import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.utils.BrowsersCache +import java.lang.ref.WeakReference /** * The main activity of the application. The application is primarily a single Activity (this one) @@ -122,7 +121,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { private var isVisuallyComplete = false - private var visualCompletenessQueue: RunWhenReadyQueue? = null private var privateNotificationObserver: PrivateNotificationFeature? = null private var isToolbarInflated = false @@ -164,13 +162,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { // Must be after we set the content view if (isVisuallyComplete) { - rootContainer.doOnPreDraw { - // This delay is temporary. We are delaying 5 seconds until the performance - // team can locate the real point of visual completeness. - it.postDelayed({ - visualCompletenessQueue!!.ready() - }, delay) - } + components.performance.visualCompletenessQueue + .attachViewToRunVisualCompletenessQueueLater(WeakReference(rootContainer)) } sessionObserver = UriOpenedObserver(this) @@ -678,9 +671,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { * The root container is null at this point, so let the HomeActivity know that * we are visually complete. */ - fun postVisualCompletenessQueue(visualCompletenessQueue: RunWhenReadyQueue) { + fun setVisualCompletenessQueueReady() { isVisuallyComplete = true - this.visualCompletenessQueue = visualCompletenessQueue } private fun captureSnapshotTelemetryMetrics() = CoroutineScope(Dispatchers.IO).launch { @@ -717,7 +709,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { const val PRIVATE_BROWSING_MODE = "private_browsing_mode" const val EXTRA_DELETE_PRIVATE_TABS = "notification_delete_and_open" const val EXTRA_OPENED_FROM_NOTIFICATION = "notification_open" - const val delay = 5000L const val START_IN_RECENTS_SCREEN = "start_in_recents_screen" // PWA must have been used within last 30 days to be considered "recently used" for the diff --git a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt index 20c32bc3a..25fa9fc40 100644 --- a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt @@ -66,6 +66,12 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) } } + override fun onDestroyView() { + super.onDestroyView() + // letting go of the resources to avoid memory leak. + adapter = null + } + private fun bindRecyclerView(view: View) { val managementView = AddonsManagementView( navController = findNavController(), diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index 42f4f4269..631eefb22 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -591,7 +591,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session if (showEngineView) { engineView?.asView()?.isVisible = true - swipeRefresh.alpha = 1f + swipeRefresh?.alpha = 1f } else { engineView?.asView()?.isVisible = false } diff --git a/app/src/main/java/org/mozilla/fenix/components/PerformanceComponent.kt b/app/src/main/java/org/mozilla/fenix/components/PerformanceComponent.kt index 615d348a8..b692d216b 100644 --- a/app/src/main/java/org/mozilla/fenix/components/PerformanceComponent.kt +++ b/app/src/main/java/org/mozilla/fenix/components/PerformanceComponent.kt @@ -5,10 +5,11 @@ package org.mozilla.fenix.components import mozilla.components.support.utils.RunWhenReadyQueue +import org.mozilla.fenix.perf.VisualCompletenessQueue /** * Component group for all functionality related to performance. */ class PerformanceComponent { - val visualCompletenessQueue by lazy { RunWhenReadyQueue() } + val visualCompletenessQueue by lazy { VisualCompletenessQueue(RunWhenReadyQueue()) } } diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt index db2e02180..f7ad064c0 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt @@ -725,7 +725,7 @@ class GleanMetricsService(private val context: Context) : MetricsService { // The code below doesn't need to execute immediately, so we'll add them to the visual // completeness task queue to be run later. - context.components.performance.visualCompletenessQueue.runIfReadyOrQueue { + context.components.performance.visualCompletenessQueue.queue.runIfReadyOrQueue { // We have to initialize Glean *on* the main thread, because it registers lifecycle // observers. However, the activation ping must be sent *off* of the main thread, // because it calls Google ad APIs that must be called *off* of the main thread. diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index a914d33b1..ace14c198 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -146,8 +146,11 @@ class HomeFragment : Fragment() { private val store: BrowserStore get() = requireComponents.core.store - private val onboarding by lazy { StrictMode.allowThreadDiskReads().resetPoliciesAfter { - FenixOnboarding(requireContext()) } } + private val onboarding by lazy { + StrictMode.allowThreadDiskReads().resetPoliciesAfter { + FenixOnboarding(requireContext()) + } + } private lateinit var homeFragmentStore: HomeFragmentStore private var _sessionControlInteractor: SessionControlInteractor? = null @@ -193,7 +196,8 @@ class HomeFragment : Fragment() { topSites = StrictMode.allowThreadDiskReads().resetPoliciesAfter { components.core.topSiteStorage.cachedTopSites }, - tip = FenixTipManager(listOf(MigrationTipProvider(requireContext()))).getTip() + tip = FenixTipManager(listOf(MigrationTipProvider(requireContext()))).getTip(), + showCollectionPlaceholder = components.settings.showCollectionsPlaceholderOnHome ) ) } @@ -201,6 +205,7 @@ class HomeFragment : Fragment() { _sessionControlInteractor = SessionControlInteractor( DefaultSessionControlController( activity = activity, + settings = components.settings, engine = components.core.engine, metrics = components.analytics.metrics, sessionManager = sessionManager, @@ -220,9 +225,9 @@ class HomeFragment : Fragment() { updateLayout(view) sessionControlView = SessionControlView( view.sessionControlRecyclerView, + viewLifecycleOwner, sessionControlInteractor, - homeViewModel, - requireComponents.core.store.state.normalTabs.isNotEmpty() + homeViewModel ) updateSessionControlView(view) @@ -516,7 +521,8 @@ class HomeFragment : Fragment() { collections = components.core.tabCollectionStorage.cachedTabCollections, mode = currentMode.getCurrentMode(), topSites = components.core.topSiteStorage.cachedTopSites, - tip = FenixTipManager(listOf(MigrationTipProvider(requireContext()))).getTip() + tip = FenixTipManager(listOf(MigrationTipProvider(requireContext()))).getTip(), + showCollectionPlaceholder = components.settings.showCollectionsPlaceholderOnHome ) ) diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt index bbe46024a..dc6b78d7d 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt @@ -41,13 +41,16 @@ data class Tab( * @property mode The state of the [HomeFragment] UI. * @property tabs The list of opened [Tab] in the [HomeFragment]. * @property topSites The list of [TopSite] in the [HomeFragment]. + * @property tip The current [Tip] to show on the [HomeFragment]. + * @property showCollectionPlaceholder If true, shows a placeholder when there are no collections. */ data class HomeFragmentState( val collections: List, val expandedCollections: Set, val mode: Mode, val topSites: List, - val tip: Tip? = null + val tip: Tip? = null, + val showCollectionPlaceholder: Boolean ) : State sealed class HomeFragmentAction : Action { @@ -55,7 +58,8 @@ sealed class HomeFragmentAction : Action { val topSites: List, val mode: Mode, val collections: List, - val tip: Tip? = null + val tip: Tip? = null, + val showCollectionPlaceholder: Boolean ) : HomeFragmentAction() @@ -66,6 +70,7 @@ sealed class HomeFragmentAction : Action { data class ModeChange(val mode: Mode) : HomeFragmentAction() data class TopSitesChange(val topSites: List) : HomeFragmentAction() data class RemoveTip(val tip: Tip) : HomeFragmentAction() + object RemoveCollectionsPlaceholder : HomeFragmentAction() } private fun homeFragmentStateReducer( @@ -93,6 +98,11 @@ private fun homeFragmentStateReducer( is HomeFragmentAction.CollectionsChange -> state.copy(collections = action.collections) is HomeFragmentAction.ModeChange -> state.copy(mode = action.mode) is HomeFragmentAction.TopSitesChange -> state.copy(topSites = action.topSites) - is HomeFragmentAction.RemoveTip -> { state.copy(tip = null) } + is HomeFragmentAction.RemoveTip -> { + state.copy(tip = null) + } + is HomeFragmentAction.RemoveCollectionsPlaceholder -> { + state.copy(showCollectionPlaceholder = false) + } } } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt index 6acca7d2e..487d34db2 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt @@ -8,11 +8,13 @@ import android.content.Context import android.view.LayoutInflater import android.view.ViewGroup import androidx.annotation.LayoutRes +import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.top.sites.TopSite +import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.tips.Tip import org.mozilla.fenix.home.OnboardingState import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionHeaderViewHolder @@ -135,7 +137,8 @@ class AdapterItemDiffCallback : DiffUtil.ItemCallback() { class SessionControlAdapter( private val interactor: SessionControlInteractor, - private val hasNormalTabsOpened: Boolean + private val viewLifecycleOwner: LifecycleOwner, + private val components: Components ) : ListAdapter(AdapterItemDiffCallback()) { // This method triggers the ComplexMethod lint error when in fact it's quite simple. @@ -150,7 +153,12 @@ class SessionControlAdapter( interactor ) NoCollectionsMessageViewHolder.LAYOUT_ID -> - NoCollectionsMessageViewHolder(view, interactor, hasNormalTabsOpened) + NoCollectionsMessageViewHolder( + view, + viewLifecycleOwner, + components.core.store, + interactor + ) CollectionHeaderViewHolder.LAYOUT_ID -> CollectionHeaderViewHolder(view) CollectionViewHolder.LAYOUT_ID -> CollectionViewHolder(view, interactor) TabInCollectionViewHolder.LAYOUT_ID -> TabInCollectionViewHolder( diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt index a5b4b7de7..73a32c5a6 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt @@ -37,6 +37,7 @@ import org.mozilla.fenix.home.HomeFragmentAction import org.mozilla.fenix.home.HomeFragmentDirections import org.mozilla.fenix.home.HomeFragmentStore import org.mozilla.fenix.settings.SupportUtils +import org.mozilla.fenix.utils.Settings import mozilla.components.feature.tab.collections.Tab as ComponentTab /** @@ -144,11 +145,17 @@ interface SessionControlController { * @see [CollectionInteractor.onAddTabsToCollectionTapped] */ fun handleCreateCollection() + + /** + * @see [CollectionInteractor.onRemoveCollectionsPlaceholder] + */ + fun handleRemoveCollectionsPlaceholder() } @Suppress("TooManyFunctions", "LargeClass") class DefaultSessionControlController( private val activity: HomeActivity, + private val settings: Settings, private val engine: Engine, private val metrics: MetricController, private val sessionManager: SessionManager, @@ -213,7 +220,11 @@ class DefaultSessionControlController( metrics.track(Event.CollectionAllTabsRestored) } - override fun handleCollectionRemoveTab(collection: TabCollection, tab: ComponentTab, wasSwiped: Boolean) { + override fun handleCollectionRemoveTab( + collection: TabCollection, + tab: ComponentTab, + wasSwiped: Boolean + ) { metrics.track(Event.CollectionTabRemoved) if (collection.tabs.size == 1) { @@ -223,7 +234,13 @@ class DefaultSessionControlController( ) val message = activity.resources.getString(R.string.delete_tab_and_collection_dialog_message) - showDeleteCollectionPrompt(collection, title, message, wasSwiped, handleSwipedItemDeletionCancel) + showDeleteCollectionPrompt( + collection, + title, + message, + wasSwiped, + handleSwipedItemDeletionCancel + ) } else { viewLifecycleScope.launch(Dispatchers.IO) { tabCollectionStorage.removeTabFromCollection(collection, tab) @@ -369,6 +386,11 @@ class DefaultSessionControlController( showTabTrayCollectionCreation() } + override fun handleRemoveCollectionsPlaceholder() { + settings.showCollectionsPlaceholderOnHome = false + fragmentStore.dispatch(HomeFragmentAction.RemoveCollectionsPlaceholder) + } + private fun showShareFragment(shareSubject: String, data: List) { val directions = HomeFragmentDirections.actionGlobalShareFragment( shareSubject = shareSubject, diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt index 644178f38..b33b97699 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt @@ -93,6 +93,11 @@ interface CollectionInteractor { * Opens the collection creator */ fun onAddTabsToCollectionTapped() + + /** + * User has removed the collections placeholder from home. + */ + fun onRemoveCollectionsPlaceholder() } interface ToolbarInteractor { @@ -256,4 +261,8 @@ class SessionControlInteractor( override fun onPaste(clipboardText: String) { controller.handlePaste(clipboardText) } + + override fun onRemoveCollectionsPlaceholder() { + controller.handleRemoveCollectionsPlaceholder() + } } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt index c2f363140..73f1585bf 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.home.sessioncontrol import android.view.View +import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -13,6 +14,7 @@ import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.top.sites.TopSite import org.mozilla.fenix.R import org.mozilla.fenix.components.tips.Tip +import org.mozilla.fenix.ext.components import org.mozilla.fenix.home.HomeFragmentState import org.mozilla.fenix.home.HomeScreenViewModel import org.mozilla.fenix.home.Mode @@ -25,7 +27,8 @@ private fun normalModeAdapterItems( topSites: List, collections: List, expandedCollections: Set, - tip: Tip? + tip: Tip?, + showCollectionsPlaceholder: Boolean ): List { val items = mutableListOf() @@ -36,8 +39,9 @@ private fun normalModeAdapterItems( } if (collections.isEmpty()) { - items.add(AdapterItem.CollectionHeader) - items.add(AdapterItem.NoCollectionsMessage) + if (showCollectionsPlaceholder) { + items.add(AdapterItem.NoCollectionsMessage) + } } else { showCollections(collections, expandedCollections, items) } @@ -68,62 +72,77 @@ private fun onboardingAdapterItems(onboardingState: OnboardingState): List = mutableListOf(AdapterItem.OnboardingHeader) // Customize FxA items based on where we are with the account state: - items.addAll(when (onboardingState) { - OnboardingState.SignedOutNoAutoSignIn -> { - listOf( - AdapterItem.OnboardingManualSignIn - ) + items.addAll( + when (onboardingState) { + OnboardingState.SignedOutNoAutoSignIn -> { + listOf( + AdapterItem.OnboardingManualSignIn + ) + } + is OnboardingState.SignedOutCanAutoSignIn -> { + listOf( + AdapterItem.OnboardingAutomaticSignIn(onboardingState) + ) + } + OnboardingState.SignedIn -> listOf() } - is OnboardingState.SignedOutCanAutoSignIn -> { - listOf( - AdapterItem.OnboardingAutomaticSignIn(onboardingState) - ) - } - OnboardingState.SignedIn -> listOf() - }) + ) - items.addAll(listOf( - AdapterItem.OnboardingSectionHeader { - val appName = it.getString(R.string.app_name) - it.getString(R.string.onboarding_feature_section_header, appName) - }, - AdapterItem.OnboardingWhatsNew, - AdapterItem.OnboardingTrackingProtection, - AdapterItem.OnboardingThemePicker, - AdapterItem.OnboardingPrivateBrowsing, - AdapterItem.OnboardingToolbarPositionPicker, - AdapterItem.OnboardingPrivacyNotice, - AdapterItem.OnboardingFinish - )) + items.addAll( + listOf( + AdapterItem.OnboardingSectionHeader { + val appName = it.getString(R.string.app_name) + it.getString(R.string.onboarding_feature_section_header, appName) + }, + AdapterItem.OnboardingWhatsNew, + AdapterItem.OnboardingTrackingProtection, + AdapterItem.OnboardingThemePicker, + AdapterItem.OnboardingPrivateBrowsing, + AdapterItem.OnboardingToolbarPositionPicker, + AdapterItem.OnboardingPrivacyNotice, + AdapterItem.OnboardingFinish + ) + ) return items } private fun HomeFragmentState.toAdapterList(): List = when (mode) { - is Mode.Normal -> normalModeAdapterItems(topSites, collections, expandedCollections, tip) + is Mode.Normal -> normalModeAdapterItems( + topSites, + collections, + expandedCollections, + tip, + showCollectionPlaceholder + ) is Mode.Private -> privateModeAdapterItems() is Mode.Onboarding -> onboardingAdapterItems(mode.state) } -private fun collectionTabItems(collection: TabCollection) = collection.tabs.mapIndexed { index, tab -> +private fun collectionTabItems(collection: TabCollection) = + collection.tabs.mapIndexed { index, tab -> AdapterItem.TabInCollectionItem(collection, tab, index == collection.tabs.lastIndex) -} + } class SessionControlView( - override val containerView: View?, + override val containerView: View, + viewLifecycleOwner: LifecycleOwner, interactor: SessionControlInteractor, - private var homeScreenViewModel: HomeScreenViewModel, - private val hasNormalTabsOpened: Boolean + private var homeScreenViewModel: HomeScreenViewModel ) : LayoutContainer { val view: RecyclerView = containerView as RecyclerView - private val sessionControlAdapter = SessionControlAdapter(interactor, hasNormalTabsOpened) + private val sessionControlAdapter = SessionControlAdapter( + interactor, + viewLifecycleOwner, + containerView.context.components + ) init { view.apply { adapter = sessionControlAdapter - layoutManager = LinearLayoutManager(containerView!!.context) + layoutManager = LinearLayoutManager(containerView.context) val itemTouchHelper = ItemTouchHelper( SwipeToDeleteCallback( diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/NoCollectionsMessageViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/NoCollectionsMessageViewHolder.kt index 00a42acdc..ed3c128d0 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/NoCollectionsMessageViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/NoCollectionsMessageViewHolder.kt @@ -6,22 +6,50 @@ package org.mozilla.fenix.home.sessioncontrol.viewholders import android.view.View import androidx.core.view.isVisible +import androidx.lifecycle.LifecycleOwner import kotlinx.android.synthetic.main.no_collections_message.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map +import mozilla.components.browser.state.selector.normalTabs +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.lib.state.ext.flowScoped +import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged import org.mozilla.fenix.R -import org.mozilla.fenix.utils.view.ViewHolder +import org.mozilla.fenix.ext.increaseTapArea import org.mozilla.fenix.home.sessioncontrol.CollectionInteractor +import org.mozilla.fenix.utils.view.ViewHolder +@OptIn(ExperimentalCoroutinesApi::class) open class NoCollectionsMessageViewHolder( view: View, - interactor: CollectionInteractor, - hasNormalTabsOpened: Boolean + viewLifecycleOwner: LifecycleOwner, + store: BrowserStore, + interactor: CollectionInteractor ) : ViewHolder(view) { init { add_tabs_to_collections_button.setOnClickListener { interactor.onAddTabsToCollectionTapped() } - add_tabs_to_collections_button.isVisible = hasNormalTabsOpened + + remove_collection_placeholder.increaseTapArea( + view.resources.getDimensionPixelSize(R.dimen.tap_increase_16) + ) + + remove_collection_placeholder.setOnClickListener { + interactor.onRemoveCollectionsPlaceholder() + } + + add_tabs_to_collections_button.isVisible = store.state.normalTabs.isNotEmpty() + + store.flowScoped(viewLifecycleOwner) { flow -> + flow.map { state -> state.normalTabs.size } + .ifChanged() + .collect { tabs -> + add_tabs_to_collections_button.isVisible = tabs > 0 + } + } } companion object { diff --git a/app/src/main/java/org/mozilla/fenix/perf/VisualCompletenessQueue.kt b/app/src/main/java/org/mozilla/fenix/perf/VisualCompletenessQueue.kt new file mode 100644 index 000000000..2dd88c289 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/perf/VisualCompletenessQueue.kt @@ -0,0 +1,33 @@ +/* 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.perf + +import android.view.View +import androidx.core.view.doOnPreDraw +import mozilla.components.support.utils.RunWhenReadyQueue +import java.lang.ref.WeakReference + +/** + * class for all functionality related to Visual completeness queue + */ +class VisualCompletenessQueue(val queue: RunWhenReadyQueue) { + @Suppress("MagicNumber") + val delay = 5000L + + /** + * + * @param containerWeakReference a weak reference to the root view of a view hierarchy. Weak + * reference is to avoid memory leak. + */ + fun attachViewToRunVisualCompletenessQueueLater(containerWeakReference: WeakReference) { + containerWeakReference.get()?.doOnPreDraw { + // This delay is temporary. We are delaying 5 seconds until the performance + // team can locate the real point of visual completeness. + it.postDelayed({ + queue.ready() + }, delay) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/session/PerformanceActivityLifecycleCallbacks.kt b/app/src/main/java/org/mozilla/fenix/session/PerformanceActivityLifecycleCallbacks.kt index 2684b0388..6f46fb997 100644 --- a/app/src/main/java/org/mozilla/fenix/session/PerformanceActivityLifecycleCallbacks.kt +++ b/app/src/main/java/org/mozilla/fenix/session/PerformanceActivityLifecycleCallbacks.kt @@ -51,7 +51,7 @@ class PerformanceActivityLifecycleCallbacks( if (activity is HomeActivity) { // We should delay the visualCompletenessQueue when reaching the HomeActivity // to ensure all tasks are delayed until after visual completeness - activity.postVisualCompletenessQueue(visualCompletenessQueue) + activity.setVisualCompletenessQueueReady() } else if (shouldStartVisualCompletenessQueueImmediately()) { // If we do not go through the home activity, we have to start the tasks // immediately to avoid spending time implementing it. diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt index acd6f1320..ed452e33b 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt @@ -25,7 +25,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ObsoleteCoroutinesApi import mozilla.components.lib.state.ext.consumeFrom import org.mozilla.fenix.BrowserDirection -import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.components.FenixSnackbar @@ -169,11 +168,7 @@ class LoginDetailFragment : Fragment(R.layout.fragment_login_detail) { } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - if (FeatureFlags.loginsEdit) { - inflater.inflate(R.menu.login_options_menu, menu) - } else { - inflater.inflate(R.menu.login_delete, menu) - } + inflater.inflate(R.menu.login_options_menu, menu) this.menu = menu } diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index 885720d07..3cd3f8399 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -249,6 +249,11 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = false ) + var showCollectionsPlaceholderOnHome by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_show_collections_placeholder_home), + default = true + ) + val isCrashReportingEnabled: Boolean get() = isCrashReportEnabledInBuild && preferences.getBoolean( diff --git a/app/src/main/res/layout/no_collections_message.xml b/app/src/main/res/layout/no_collections_message.xml index a37d36062..472259061 100644 --- a/app/src/main/res/layout/no_collections_message.xml +++ b/app/src/main/res/layout/no_collections_message.xml @@ -2,42 +2,63 @@ - + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginBottom="12dp" + android:background="@drawable/empty_session_control_background" + android:orientation="vertical" + android:padding="16dp"> + app:fontFamily="@font/metropolis_semibold" + app:layout_constraintEnd_toStartOf="@id/remove_collection_placeholder" + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/no_collections_header" /> - + android:visibility="gone" + app:icon="@drawable/ic_tab_collection" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/no_collections_description" /> + diff --git a/app/src/main/res/layout/private_browsing_description.xml b/app/src/main/res/layout/private_browsing_description.xml index d0df46bd0..d964945a0 100644 --- a/app/src/main/res/layout/private_browsing_description.xml +++ b/app/src/main/res/layout/private_browsing_description.xml @@ -16,12 +16,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="none" - android:gravity="center_vertical" android:lineSpacingExtra="6dp" android:paddingHorizontal="4dp" android:paddingTop="4dp" android:scrollHorizontally="false" + android:textAlignment="viewStart" android:textColor="?primaryText" + android:textDirection="locale" android:textSize="14sp" tools:text="@string/private_browsing_placeholder_description_2" /> diff --git a/app/src/main/res/menu/login_delete.xml b/app/src/main/res/menu/login_delete.xml deleted file mode 100644 index c2866be61..000000000 --- a/app/src/main/res/menu/login_delete.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml index a2e72cc6b..9582dcb4c 100644 --- a/app/src/main/res/values-ast/strings.xml +++ b/app/src/main/res/values-ast/strings.xml @@ -15,10 +15,10 @@ Gueta o introduz una direición - Equi van amosase les llingüetes abiertes. + Equí van amosase les llingüetes abiertes. - Equi van amosase les llingüetes abiertes. + Equí van amosase les llingüetes abiertes. 1 llingüeta abierta. Toca pa cambiar a otra. @@ -128,6 +128,8 @@ Cola potencia de %1$s Mou de llector + + Zarrar la vista de llector Abrir nuna aplicación @@ -895,8 +897,7 @@ ¿Tienes entrugues tocante al rediseñu de %s?¿Quies saber qué camudó? Consigui rempuestes equí - + Aprovecha %s al máximu. Aniciando sesión… diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 78d1ec9fb..1b2d9ab36 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -305,9 +305,14 @@ Obre els enllaços en les aplicacions + + Gestor de baixades extern Complements + + Notificacions + Sincronitza ara @@ -576,6 +581,13 @@ No hi ha informació d’historial + + + No hi ha cap baixada + + %1$d seleccionades + El %1$s no pot carregar aquesta pàgina. @@ -983,9 +995,10 @@ Teniu preguntes sobre el redisseny del %s? Voleu saber què ha canviat? Vegeu les respostes aquí - + Traieu tot el profit al %s. + + Més informació @@ -1476,6 +1489,9 @@ Inicia la sessió per sincronitzar + + No hi ha cap pestanya oberta + S’ha arribat al límit de llocs principals diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 933406e25..a5447f2d6 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -307,6 +307,9 @@ Dodaci + + Obavijesti + Sinkroniziraj sada @@ -581,6 +584,13 @@ Nema povijesti + + + Ovdje nema preuzimanja + + Odabrano: %1$d + Oprosti. %1$s ne može učitati tu stranicu. @@ -988,9 +998,10 @@ Imaš pitanja o redizajniranom %su? Želiš znati što se promijenilo? Potraži odgovore ovdje - + Iskoristi sve prednosti %sa + + Saznaj više @@ -1488,6 +1499,9 @@ Prijavi se za sinkronizaciju + + Nema otvorenih kartica + Dostignuto ograničenje za omiljene stranice diff --git a/app/src/main/res/values-hy-rAM/strings.xml b/app/src/main/res/values-hy-rAM/strings.xml index 3838db250..5b68f0135 100644 --- a/app/src/main/res/values-hy-rAM/strings.xml +++ b/app/src/main/res/values-hy-rAM/strings.xml @@ -49,7 +49,7 @@ Ընտրված - %1$s-ն արտադրվում է Mozilla-ի կողմից: + %1$s-ը մշակված է Mozilla-ի կողմից: @@ -184,9 +184,9 @@ Չթույլատրել - Թույլատրեք որոնման առաջարկներ մասնավոր աշխատաշրջաններում: + Թույլատրել որոնման առաջարկներ մասնավոր աշխատաշրջաններում: - %s-ը համօգտագործելու է այն ամենը, ինչ մուտքագրում եք հասցեի տողում՝ ձեր սկզբնական որոնիչի հետ: + %s-ը համօգտագործելու է այն ամենը, ինչ մուտքագրում եք հասցեի տողում ձեր սկզբնական որոնիչի հետ: Իմանալ ավելին @@ -266,13 +266,13 @@ Հարմարեցնել - Համաժամեցեք էջանիշները, պատմությունը և ավելին ձեր Firefox հաշվի հետ + Համաժամեցրեք էջանիշները, պատմությունը և ավելին ձեր Firefox հաշվի հետ Firefox-ի հաշիվ Կրկին կապակցվեք՝ համաժամեցումը վերսկսելու համար - Լեզու + Լեզուն Տվյալների ընտրություն @@ -303,9 +303,14 @@ Հաշվի կարգավորումներ Բացել հղումները հավելվածներում + + Ներբեռնումների արտաքին հավելված Հավելումներ + + Ծանուցումներ + Համաժամեցնել @@ -377,7 +382,7 @@ Telemetry - Օգտագործում և տեխնիկական տվյալներ + Օգտագործման և տեխնիկական տվյալներ Կիսվում է արագագործության, օգտագործման, սարքակազմի և ձեր դիտարկիչի կարգավորումներով Mozilla-ի հետ՝ %1$s-ը էլ ավելի լավը դարձնելու համար @@ -572,6 +577,13 @@ Այստեղ պատմություն չկա + + + Չկան նեևբեռնումներ + + Ընտրված է %1$d-ը + Ներողություն. %1$s-ը չի կարող բեռնել այդ էջը: @@ -622,7 +634,7 @@ Բացել նոր ներդիր - Բացել Գաղտնի ներդիրով + Բացել Գաղտնի ներդիրում Ջնջել @@ -894,7 +906,7 @@ %d ներդիրներ - Պատմության և կայքի տվյալների դիտարկում + Դիտարկման պատմություն և կայքերի տվյալներ %d հասցեներ @@ -906,13 +918,13 @@ Cookie-ներ - Դուք դուրս կգրվեք կայքերի մեծ մասից + Դուրս կգրվեք կայքերի մեծ մասից - Պահված պատկերներն ու ֆայլերը + Շտեմված պատկերներ ու ֆայլեր Ազատում է պահեստային տարածք - Կայքի թույլտվություններ + Կայքերի թույլտվություններ Ջնջել դիտարկման տվյալները @@ -982,6 +994,8 @@ Ստացեք պատասխանները այստեղ Օգտագործեք առավելագույնը %s-ից: + + Իմանալ ավելին @@ -1016,16 +1030,16 @@ - Դիրքորոշեք + Դիրքորոշել - Փորձեք մի ձեռքով դիտարկումը գործիքագոտու ներքևում կամ այն տեղափոխել վերև: + Փորձեք մի ձեռքով դիտարկումը գործիքագոտու ներքևում կամ այն տեղափոխեք վերև: - Դիտարկեք գաղտնի + Դիտարկել գաղտնի - Մեկ անգամ բացել գաղտնի ներդիր. Հպեք %s պատկերակին: + Ցանկանում եք բացել գաղտնի ներդիր, հպեք %s պատկերակին: - Ամեն անգամ բացել գաղտնի ներդիրները. Թարմացրեք ձեր գաղտնի դիտարկման կարգավորումները: + Ցանկանում եք ամեն անգամ բացել գաղտնի ներդիրները. Թարմացրեք գաղտնի դիտարկման կարգավորումները: Բացել կարգավորումները @@ -1033,8 +1047,8 @@ - Մենք պատրաստել ենք %s-ը, որպեսզի տանք Ձեզ հսկողություն սահմանելու այն ամենի նկատմամբ, ինչ որ համօգտագործում եք - առցանց և այն, ինչ դուք համօգտագործում եք մեզ հետ: + Մենք պատրաստել ենք %s-ը, որպեսզի Ձեզ տանք հսկողություն սահմանելը այն ամենի նկատմամբ, ինչ որ համօգտագործում եք + առցանց և մեզ հետ: Կարդացեք մեր գաղտնիության ծանուցումը @@ -1075,7 +1089,7 @@ Մուտք գործեք ձեր տեսախցիկի հետ - Փոխարենը օգտագործեք էլ. փոստ + Փոխարենը օգտ. էլ. փոստ Firefox-ը կկանգնեցնի համաժամացումը ձեր հաշվի հետ, բայց չի ջնջվի այս սարքում ձեր դիտարկման որևէ տվյալ։ @@ -1095,7 +1109,7 @@ Դիտարկեք առանց Ձեզ հետևելու - Ձեր տվյալները պահեք ձեզ մոտ: %s-ը ձեզ պաշտպանում է ամենատարածված հետագծիչներից, որոնք հետևում են այն ամենին, ինչ որ անում եք առցանց: + Ձեր տվյալները պահեք ձեզ մոտ: %s-ը պաշտպանում է ամենատարածված հետագծիչներից, որոնք հետևում են այն ամենին, ինչ որ անում եք առցանց: Իմանալ ավելին @@ -1253,7 +1267,7 @@ Մուտք գործեք՝ համաժամեցնելու համար - Պահպանված մուտքաանուններ + Պահպանված մուտքանուններ Ձեր պահպանած կամ համաժամեցրած մուտքանունները կցուցադրվեն այստեղ: @@ -1288,7 +1302,7 @@ Իմանալ ավելին - Ցանկանո՞ւմ եք, որ %s-ը պահպանի այս մուտքաանունը: + Ցանկանո՞ւմ եք, որ %s-ը պահպանի այս մուտքանունը: Պահպանել @@ -1354,7 +1368,7 @@ Անուն - Որոնել տողը՝ օգտագործելու համար + Օգտագործվող որոնման տողը Հարցումը փոխարինել “%s”-ով: Օրինակ՝ \nhttps://www.google.com/search?q=%s @@ -1369,7 +1383,7 @@ «%s» անունով որոնիչ արդեն գոյություն ունի: - Մուտքագրել որոնման տող + Մուտքագրեք որոնման տող Ստուգեք, որ որոնման տողը համապատասխանում է օրինակի ձևաչափին @@ -1382,10 +1396,10 @@ %s-ը ջնջվել է - Բարի գալուստ բոլորովին նոր %s + Բարի գալուստ նոր %s - Ամբողջովին վերափոխված դիտարկիչը Ձեզ է սպասում՝ բարելավված արտադրողականությամբ և յուրահատկություններով, որոնք կօգնեն ավելին անել առցանց:\n\nԽնդրում ենք սպասել, մինչ մենք կթարմացնենք %s-ը ձեր + Ամբողջովին վերափոխված դիտարկիչը Ձեզ է սպասում՝ բարելավված արտադրողականությամբ և յուրահատկություններով, որոնք կօգնեն անել ավելին առցանց:\n\nԽնդրում ենք սպասել, մինչ մենք կթարմացնենք %s-ի հետևյալ բաղադրիչները՝ %s-ը արդիացվում է… @@ -1415,7 +1429,7 @@ Համոզվա՞ծ եք, որ ցանկանում եք մաքրել այս թույլտվությունը այս կայքում: - Կայքի բացառություններ չեն + Բացառություններ չկան Լավագույն հոդվածներ @@ -1430,7 +1444,7 @@ Խմբագրել - Համոզվա՞ծ եք, որ ցանկանում եք ջնջել այս մուտքագրումը: + Համոզվա՞ծ եք, որ ցանկանում եք ջնջել այս մուտքանունը: Ջնջել @@ -1455,7 +1469,7 @@ Խոսել - Այդ անունով մուտքագրում արդեն գոյություն ունի + Այդ անունով մուտքանուն արդեն գոյություն ունի @@ -1472,6 +1486,9 @@ Մուտք գործեք՝ համաժամեցնելու համար + + Չկան բացված ներդիրներ + Լավագույն կայքերի ցանկը լրացել է diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 43057ff91..aaa225684 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -1061,7 +1061,7 @@ 자동 개인정보 보호 - 개인 정보 및 보안 설정은 추적기, 멀웨어 및 사용자를 따라다니는 회사를 차단합니다. + 개인 정보 및 보안 설정은 추적기, 악성 코드 및 사용자를 따라다니는 회사를 차단합니다. 표준 (기본값) diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index 16f141ca8..a98202dab 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -43,6 +43,8 @@ %1$s deseleccionat Sortida del mòde seleccion multipla + + Mòde de seleccion multipla activat, seleccionatz los onglets d’enregistrar dins una colleccion Seleccionat @@ -382,8 +384,12 @@ Telemetria Donadas tecnicas e d’utilizacion + + Parteja d’informacions sus las performàncias, lo material, l’utilizacion e las personalizacions a Mozilla per nos ajudar a melhorar %1$s Donadas marketing + + Parteja de donadas sus las foncionalitats qu’utilizatz amb Leanplum, nòstre provesidor de marketing mobil. Experimentacions @@ -744,6 +750,8 @@ Menú de colleccion Amassatz çò que compta per vos + + Agropatz de recèrcas, de sites e d’onglets similaris per i accedir rapidament mai tard. Seleccionar d’onglets @@ -807,6 +815,8 @@ Fòra linha Connectar un periferic mai + + Per enviar un onglet, connectatz-vos a vòstre compte Firefox sus almens un autre periferic. Comprés ! @@ -988,6 +998,8 @@ Sincro. activada Fracàs de connexion + + Confidencialitat automatica Los paramètres de confidencialitat e de seguretat blocan los traçadors, los logicials malvolents e las entrepresas que vos pistan. @@ -1023,6 +1035,8 @@ Causissètz vòstre tèma + + Estalviatz la batariá e vòstra vista en activant lo mòde fosc. Automatic @@ -1156,6 +1170,8 @@ Plantatges + + Vòstres dreches Entresenhas sus la licéncia diff --git a/app/src/main/res/values-rm/strings.xml b/app/src/main/res/values-rm/strings.xml index c438b914b..2fe0bdf1c 100644 --- a/app/src/main/res/values-rm/strings.xml +++ b/app/src/main/res/values-rm/strings.xml @@ -303,6 +303,9 @@ Supplements + + Communicaziuns + Sincronisar ussa @@ -569,6 +572,13 @@ Nagina cronologia + + + Naginas telechargiadas disponiblas + + %1$d tschernidas + Perstgisa. %1$s na po betg chargiar questa pagina. @@ -975,9 +985,10 @@ Has ti dumondas areguard il nov %s? Vuls savair tge ch\'è sa midà? Qua datti respostas - + Rablar ora il maximum da %s. + + Ulteriuras infurmaziuns @@ -1476,6 +1487,9 @@ S\'annunziar tar Sync + + Nagins tabs averts + Cuntanschì la limita da paginas preferidas diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1c5b5a113..1aed40193 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -225,7 +225,7 @@ Справка - Оценить на Google Play + Оценить в Google Play Оставить отзыв Дополнения + + Уведомления + Синхронизировать @@ -587,6 +590,13 @@ История отсутствует + + + Здесь ещё нет загрузок + + Выбрано: %1$d + Извините, %1$s не смог загрузить эту страницу. @@ -1013,9 +1023,10 @@ Есть вопросы о переработанном %s? Хотите узнать, что изменилось? Получите ответы здесь - + Получите максимум от %s. + + Узнать больше @@ -1085,11 +1096,11 @@ Сохраните заряд аккумулятора и ваше зрение, включив тёмную тему. - Автоматически + Автоматическая Адаптируется к настройкам вашего устройства - Темная тема + Тёмная тема Светлая тема @@ -1503,6 +1514,9 @@ Войти в Синхронизацию + + Нет открытых вкладок + Достигнут лимит топа сайтов diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 4a21201b2..d69797c5e 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -306,6 +306,9 @@ Додаци + + Обавештења + Синхронизуј сада @@ -574,6 +577,13 @@ Овде нема историјата + + + Овде нема преузимања + + Изабрано %1$d + Нажалост, %1$s не може учитати ту страницу. @@ -981,9 +991,10 @@ Имате питање о новом дизајну апликације %s? Желите ли да знате шта је промењено? Овде потражите одговоре - + Искористите %s у потпуности. + + Сазнајте више @@ -1478,6 +1489,9 @@ Пријавите се на Sync + + Нема отворених језичака + Достигнуто је ограничење популарних страница diff --git a/app/src/main/res/values-su/strings.xml b/app/src/main/res/values-su/strings.xml index 884215ed1..ae7b052b2 100644 --- a/app/src/main/res/values-su/strings.xml +++ b/app/src/main/res/values-su/strings.xml @@ -308,6 +308,9 @@ Émbohan + + Iber + Singkronkeun ayeuna @@ -578,6 +581,13 @@ Teu aya jujutan di dieu + + + Taya undeuran di dieu + + %1$d dipilih + Hampura. %1$s teu tiasa nyungsi ieu kaca. @@ -989,9 +999,10 @@ Boga patalekan ngeunaan rarancang anyar %s? Hoyong uninga naon anu robah? Kéngingkeun waleran di dieu - + Maksimalkeun %s. + + Lenyepan @@ -1486,6 +1497,9 @@ Asup pikeun nyingkronkeun + + Taya tab muka + Wates loka top geus kahontal diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 47bc4e334..6c01291ef 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -172,4 +172,5 @@ 48dp + 16dp diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 6b0042d96..621b377d7 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -195,4 +195,6 @@ pref_key_default_browser pref_key_login_exceptions + + pref_key_show_collections_home diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1f23516b0..cdcd98611 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -725,10 +725,8 @@ Collections Collection menu - - Collect the things that matter to you - Group together similar searches, sites, and tabs for quick access later. + Collect the things that matter to you.\nGroup together similar searches, sites, and tabs for quick access later. Select Tabs @@ -1467,4 +1465,6 @@ OK, Got It + + Remove diff --git a/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt b/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt index bf63fcce6..8c0b4c6f8 100644 --- a/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt @@ -78,8 +78,10 @@ class DefaultSessionControlControllerTest { collections = emptyList(), expandedCollections = emptySet(), mode = Mode.Normal, - topSites = emptyList() + topSites = emptyList(), + showCollectionPlaceholder = true ) + every { sessionManager.sessions } returns emptyList() every { navController.currentDestination } returns mockk { every { id } returns R.id.homeFragment @@ -94,6 +96,7 @@ class DefaultSessionControlControllerTest { controller = DefaultSessionControlController( activity = activity, + settings = settings, engine = engine, metrics = metrics, sessionManager = sessionManager, @@ -414,4 +417,14 @@ class DefaultSessionControlControllerTest { ) } } + + @Test + fun handleRemoveCollectionsPlaceholder() { + controller.handleRemoveCollectionsPlaceholder() + + verify { + settings.showCollectionsPlaceholderOnHome = false + fragmentStore.dispatch(HomeFragmentAction.RemoveCollectionsPlaceholder) + } + } } diff --git a/app/src/test/java/org/mozilla/fenix/home/HomeFragmentStoreTest.kt b/app/src/test/java/org/mozilla/fenix/home/HomeFragmentStoreTest.kt index ed217152f..87baabbb7 100644 --- a/app/src/test/java/org/mozilla/fenix/home/HomeFragmentStoreTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/HomeFragmentStoreTest.kt @@ -12,6 +12,7 @@ import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.top.sites.TopSite import mozilla.components.service.fxa.manager.FxaAccountManager import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -53,7 +54,8 @@ class HomeFragmentStoreTest { collections = emptyList(), expandedCollections = emptySet(), mode = currentMode.getCurrentMode(), - topSites = emptyList() + topSites = emptyList(), + showCollectionPlaceholder = true ) homeFragmentStore = HomeFragmentStore(homeFragmentState) @@ -95,6 +97,15 @@ class HomeFragmentStoreTest { assertEquals(topSites, homeFragmentStore.state.topSites) } + @Test + fun `Test changing hiding collections placeholder`() = runBlocking { + assertTrue(homeFragmentStore.state.showCollectionPlaceholder) + + homeFragmentStore.dispatch(HomeFragmentAction.RemoveCollectionsPlaceholder).join() + + assertFalse(homeFragmentStore.state.showCollectionPlaceholder) + } + @Test fun `Test changing the expanded collections in HomeFragmentStore`() = runBlocking { val collection: TabCollection = mockk().apply { @@ -110,25 +121,27 @@ class HomeFragmentStoreTest { } @Test - fun `Test changing the collections, mode and top sites in the HomeFragmentStore`() = runBlocking { - // Verify that the default state of the HomeFragment is correct. - assertEquals(0, homeFragmentStore.state.collections.size) - assertEquals(0, homeFragmentStore.state.topSites.size) - assertEquals(Mode.Normal, homeFragmentStore.state.mode) + fun `Test changing the collections, mode and top sites in the HomeFragmentStore`() = + runBlocking { + // Verify that the default state of the HomeFragment is correct. + assertEquals(0, homeFragmentStore.state.collections.size) + assertEquals(0, homeFragmentStore.state.topSites.size) + assertEquals(Mode.Normal, homeFragmentStore.state.mode) - val collections: List = listOf(mockk()) - val topSites: List = listOf(mockk(), mockk()) + val collections: List = listOf(mockk()) + val topSites: List = listOf(mockk(), mockk()) - homeFragmentStore.dispatch( - HomeFragmentAction.Change( - collections = collections, - mode = Mode.Private, - topSites = topSites - ) - ).join() + homeFragmentStore.dispatch( + HomeFragmentAction.Change( + collections = collections, + mode = Mode.Private, + topSites = topSites, + showCollectionPlaceholder = true + ) + ).join() - assertEquals(1, homeFragmentStore.state.collections.size) - assertEquals(Mode.Private, homeFragmentStore.state.mode) - assertEquals(2, homeFragmentStore.state.topSites.size) - } + assertEquals(1, homeFragmentStore.state.collections.size) + assertEquals(Mode.Private, homeFragmentStore.state.mode) + assertEquals(2, homeFragmentStore.state.topSites.size) + } } diff --git a/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt index 63bc74661..6357cf51f 100644 --- a/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt @@ -110,4 +110,10 @@ class SessionControlInteractorTest { interactor.onPasteAndGo("text") verify { controller.handlePasteAndGo("text") } } + + @Test + fun onRemoveCollectionsPlaceholder() { + interactor.onRemoveCollectionsPlaceholder() + verify { controller.handleRemoveCollectionsPlaceholder() } + } } diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/NoCollectionsMessageViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/NoCollectionsMessageViewHolderTest.kt index 54b50341c..643a1e6fd 100644 --- a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/NoCollectionsMessageViewHolderTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/NoCollectionsMessageViewHolderTest.kt @@ -8,9 +8,13 @@ import android.view.LayoutInflater import android.view.View import androidx.appcompat.view.ContextThemeWrapper import androidx.core.view.isVisible +import androidx.lifecycle.LifecycleOwner import io.mockk.mockk import io.mockk.verify import kotlinx.android.synthetic.main.no_collections_message.view.* +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.createTab +import mozilla.components.browser.state.store.BrowserStore import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -25,6 +29,14 @@ import org.mozilla.fenix.home.sessioncontrol.CollectionInteractor class NoCollectionsMessageViewHolderTest { private lateinit var view: View + private val store: BrowserStore = BrowserStore( + initialState = BrowserState( + listOf( + createTab("https://www.mozilla.org", id = "reader-inactive-tab") + ) + ) + ) + private lateinit var lifecycleOwner: LifecycleOwner private lateinit var interactor: CollectionInteractor @Before @@ -32,28 +44,40 @@ class NoCollectionsMessageViewHolderTest { val appCompatContext = ContextThemeWrapper(testContext, R.style.NormalTheme) view = LayoutInflater.from(appCompatContext) .inflate(NoCollectionsMessageViewHolder.LAYOUT_ID, null) + lifecycleOwner = mockk(relaxed = true) interactor = mockk(relaxed = true) } @Test - fun `hide button when hasNormalTabsOpened is false`() { - NoCollectionsMessageViewHolder(view, interactor, hasNormalTabsOpened = false) + fun `hide add to collection button when there are no tabs open`() { + val noTabsStore = BrowserStore() + NoCollectionsMessageViewHolder(view, lifecycleOwner, noTabsStore, interactor) assertFalse(view.add_tabs_to_collections_button.isVisible) } @Test - fun `show button when hasNormalTabsOpened is true`() { - NoCollectionsMessageViewHolder(view, interactor, hasNormalTabsOpened = true) + fun `show add to collection button when there are tabs`() { + NoCollectionsMessageViewHolder(view, lifecycleOwner, store, interactor) assertTrue(view.add_tabs_to_collections_button.isVisible) } @Test fun `call interactor on click`() { - NoCollectionsMessageViewHolder(view, interactor, hasNormalTabsOpened = true) + NoCollectionsMessageViewHolder(view, lifecycleOwner, store, interactor) view.add_tabs_to_collections_button.performClick() verify { interactor.onAddTabsToCollectionTapped() } } + + @Test + fun `hide view and change setting on remove placeholder click`() { + NoCollectionsMessageViewHolder(view, lifecycleOwner, store, interactor) + + view.remove_collection_placeholder.performClick() + verify { + interactor.onRemoveCollectionsPlaceholder() + } + } } diff --git a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt index 6bb0b26b5..6e1e9a51d 100644 --- a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt +++ b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt @@ -289,6 +289,19 @@ class SettingsTest { assertFalse(settings.shouldUseTrackingProtection) } + @Test + fun shouldShowCollectionsPlaceholderOnHome() { + // When + // Then + assertTrue(settings.showCollectionsPlaceholderOnHome) + + // When + settings.showCollectionsPlaceholderOnHome = false + + // Then + assertFalse(settings.showCollectionsPlaceholderOnHome) + } + @Test fun shouldSetOpenInAppOpened() { // When diff --git a/docs/crash-reporting.md b/docs/crash-reporting.md index 7a0c92a97..66476b138 100644 --- a/docs/crash-reporting.md +++ b/docs/crash-reporting.md @@ -1,6 +1,6 @@ # Crash Reporting -Firefox Preview uses a few libraries for crash and exception reporting. This kind of reporting gives Mozilla invaluable insight as to why Firefox Preview crashes or incorrectly behaves. It is one of the key methods we use to improve the product in terms of stability. +Firefox for Android uses a few libraries for crash and exception reporting. This kind of reporting gives Mozilla invaluable insight as to why Firefox for Android crashes or incorrectly behaves. It is one of the key methods we use to improve the product in terms of stability. This page documents the types of crash reporting, how the various parts interact, and what kind of data is sent back to Mozilla. @@ -8,7 +8,7 @@ Documentation for the specific libraries is included in the [Android Components ## Glean crash ping -[Glean SDK](https://mozilla.github.io/glean/book/index.html) is a Mozilla open source telemetry library, which Firefox Preview uses to collect app telemetry. It can also collect crash counts as a labeled counter with each label corresponding to a specific type of crash (such as `native_code_crash`, `unhandled_exception`). +[Glean SDK](https://mozilla.github.io/glean/book/index.html) is a Mozilla open source telemetry library, which Firefox for Android uses to collect app telemetry. It can also collect crash counts as a labeled counter with each label corresponding to a specific type of crash (such as `native_code_crash`, `unhandled_exception`). The Glean crash ping format is documented [here](https://github.com/mozilla-mobile/android-components/blob/master/components/lib/crash/docs/metrics.md). @@ -24,11 +24,11 @@ In [HomeActivity](https://github.com/mozilla-mobile/fenix/blob/master/app/src/ma ## Socorro -[Socorro](https://wiki.mozilla.org/Socorro) is a Mozilla open source project for [crash statistics](https://crash-stats.mozilla.org/). Firefox Preview uses Socorro to track native GeckoView crashes. Crash reports contain a signature, classifications, and a number of improved fields (e.g. OS, product, version) - you can read more about what is sent in these fields in the [Socorro report documentation](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Crash_reporting/Understanding_crash_reports). +[Socorro](https://wiki.mozilla.org/Socorro) is a Mozilla open source project for [crash statistics](https://crash-stats.mozilla.org/). Firefox for Android uses Socorro to track native GeckoView crashes. Crash reports contain a signature, classifications, and a number of improved fields (e.g. OS, product, version) - you can read more about what is sent in these fields in the [Socorro report documentation](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Crash_reporting/Understanding_crash_reports). These crashes contain hardware information and some app metadata, but no personally identifiable information is visible in the report. A few privacy-sensitive parts are only available to users who have "minidump access", which is a relatively small number of users with specific rules they must follow. -A sample Firefox Preview crash report can be found [here](https://crash-stats.mozilla.org/report/index/bbbcc019-f30c-4fbb-8cbd-543940190923). +A sample Firefox for Android crash report can be found [here](https://crash-stats.mozilla.org/report/index/bbbcc019-f30c-4fbb-8cbd-543940190923). A crash report is only sent when the user confirms and submits it through the crash reporter notification or dialog. Crash reports are never automatically sent. @@ -40,11 +40,11 @@ A crash report is only sent when the user confirms and submits it through the cr ### High-Level Summary -The server is hosted and maintained by Mozilla. There are no third-parties involved, all crash reports are sent directly from Firefox Preview to the Sentry server hosted by Mozilla. +The server is hosted and maintained by Mozilla. There are no third-parties involved, all crash reports are sent directly from Firefox for Android to the Sentry server hosted by Mozilla. On the client side Sentry is invisible. There are no parts to interact with. It reports crashes and fatal errors back to Mozilla in the background. -On the server side there is a dashboard that the Firefox Preview team uses to look at incoming crash reports. The dashboard lets us inspect the crash report in detail and for example see where in the application the crash happened, what version of the application was used and what version of Android OS was active. Below is an overview of all the attributes that are part of a crash report. +On the server side there is a dashboard that the Firefox for Android team uses to look at incoming crash reports. The dashboard lets us inspect the crash report in detail and for example see where in the application the crash happened, what version of the application was used and what version of Android OS was active. Below is an overview of all the attributes that are part of a crash report. ### Sentry Reports @@ -101,7 +101,7 @@ Sentry collects basic information about the device the application is running on ### Application Information -Sentry collects basic information about the Firefox Preview app. +Sentry collects basic information about the Firefox for Android app. ``` "app":{ @@ -122,7 +122,7 @@ Sentry collects basic information about the Firefox Preview app. #### Stack trace -Every crash report contains a *stack trace*, which shows what functions in the Firefox Preview code led to this crash. It includes names of Android framework functions and Firefox Preview functions. Here's an excerpt of three lines from the stack trace: +Every crash report contains a *stack trace*, which shows what functions in the Firefox for Android code led to this crash. It includes names of Android framework functions and Firefox for Android functions. Here's an excerpt of three lines from the stack trace: ``` "sentry.interfaces.Exception": { @@ -158,7 +158,7 @@ Every crash report contains a *stack trace*, which shows what functions in the F ##### Exception message -The first line of every stack trace in every crash report contains a *reason* - why did this crash happen. This reason is provided by the developers who wrote the code that decide the app is in an error state. These developers include the Firefox Preview team at Mozilla, the Android framework, the Java programming language, and any libraries Mozilla bundles to develop Firefox Preview. +The first line of every stack trace in every crash report contains a *reason* - why did this crash happen. This reason is provided by the developers who wrote the code that decide the app is in an error state. These developers include the Firefox for Android team at Mozilla, the Android framework, the Java programming language, and any libraries Mozilla bundles to develop Firefox for Android. Java, the Android framework, and Mozilla are diligent about making sure that no personally identifiable information is put in any of these messages. We keep them technical and to the point. We at Mozilla stay on top of our dependencies to ensure they're not including personally identifiable information as well. @@ -167,7 +167,7 @@ Here's an example message generated by Java: java.lang.StringIndexOutOfBoundsException: length=0; regionStart=20; regionLength=20 ``` -Example of a Firefox Preview generated message: +Example of a Firefox for Android generated message: ``` java.lang.StringIndexOutOfBoundsException: Cannot create negative-length String ``` diff --git a/docs/telemetry.md b/docs/telemetry.md index fb73dfc4a..d87bfb185 100644 --- a/docs/telemetry.md +++ b/docs/telemetry.md @@ -8,7 +8,7 @@ By using the Glean SDK, Fenix can send the pings the SDK owns and defines, as do Additional metrics or pings defined by Fenix are documented in the [Glean SDK autogenerated docs](metrics.md). ## Leanplum -See [here](https://github.com/mozilla-mobile/fenix/blob/master/docs/mma.md) for details on Leanplum usage in Firefox Preview. +See [here](https://github.com/mozilla-mobile/fenix/blob/master/docs/mma.md) for details on Leanplum usage in Firefox for Android. ## Crash reporting -See [here](https://github.com/mozilla-mobile/fenix/blob/master/docs/crash-reporting.md) for details on crash reporting in Firefox Preview. +See [here](https://github.com/mozilla-mobile/fenix/blob/master/docs/crash-reporting.md) for details on crash reporting in Firefox for Android.