For #7092: Add initial Migration UI
parent
29d9ba472c
commit
b89afe7b7c
|
@ -429,10 +429,7 @@ dependencies {
|
|||
implementation Deps.mozilla_support_utils
|
||||
implementation Deps.mozilla_support_locale
|
||||
|
||||
// We only care about support-migration in builds that will be overwriting Fennec.
|
||||
fennecProductionImplementation Deps.mozilla_support_migration
|
||||
fennecBetaImplementation Deps.mozilla_support_migration
|
||||
fennecNightlyImplementation Deps.mozilla_support_migration
|
||||
implementation Deps.mozilla_support_migration
|
||||
|
||||
implementation Deps.mozilla_ui_colors
|
||||
implementation Deps.mozilla_ui_icons
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
History
|
||||
Bookmarks
|
||||
Logins
|
||||
Open Tabs
|
||||
Settings
|
|
@ -52,7 +52,8 @@ class IntentReceiverActivity : Activity() {
|
|||
)
|
||||
}
|
||||
|
||||
val intentProcessors = components.intentProcessors.externalAppIntentProcessors +
|
||||
val intentProcessors = listOf(components.intentProcessors.migrationIntentProcessor) +
|
||||
components.intentProcessors.externalAppIntentProcessors +
|
||||
modeDependentProcessors +
|
||||
NewTabShortcutIntentProcessor()
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ package org.mozilla.fenix.components
|
|||
|
||||
import android.content.Context
|
||||
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
|
||||
import mozilla.components.support.migration.state.MigrationStore
|
||||
import org.mozilla.fenix.test.Mockable
|
||||
import org.mozilla.fenix.utils.ClipboardHandler
|
||||
|
||||
|
@ -44,10 +45,12 @@ class Components(private val context: Context) {
|
|||
useCases.sessionUseCases,
|
||||
useCases.searchUseCases,
|
||||
core.client,
|
||||
core.customTabsStore
|
||||
core.customTabsStore,
|
||||
migrationStore
|
||||
)
|
||||
}
|
||||
val analytics by lazy { Analytics(context) }
|
||||
val publicSuffixList by lazy { PublicSuffixList(context) }
|
||||
val clipboardHandler by lazy { ClipboardHandler(context) }
|
||||
val migrationStore by lazy { MigrationStore() }
|
||||
}
|
||||
|
|
|
@ -7,10 +7,11 @@ package org.mozilla.fenix.components
|
|||
import android.content.Intent
|
||||
import mozilla.components.feature.intent.processing.IntentProcessor
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.MigrationProgressActivity
|
||||
import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity
|
||||
|
||||
enum class IntentProcessorType {
|
||||
EXTERNAL_APP, NEW_TAB, OTHER;
|
||||
EXTERNAL_APP, NEW_TAB, MIGRATION, OTHER;
|
||||
|
||||
/**
|
||||
* The destination activity based on this intent
|
||||
|
@ -19,6 +20,7 @@ enum class IntentProcessorType {
|
|||
get() = when (this) {
|
||||
EXTERNAL_APP -> ExternalAppBrowserActivity::class.java.name
|
||||
NEW_TAB, OTHER -> HomeActivity::class.java.name
|
||||
MIGRATION -> MigrationProgressActivity::class.java.name
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,7 +29,7 @@ enum class IntentProcessorType {
|
|||
fun shouldOpenToBrowser(intent: Intent): Boolean = when (this) {
|
||||
EXTERNAL_APP -> true
|
||||
NEW_TAB -> intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY == 0
|
||||
OTHER -> false
|
||||
MIGRATION, OTHER -> false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,6 +37,7 @@ enum class IntentProcessorType {
|
|||
* Classifies the [IntentProcessorType] based on the [IntentProcessor] that handled the [Intent].
|
||||
*/
|
||||
fun IntentProcessors.getType(processor: IntentProcessor?) = when {
|
||||
migrationIntentProcessor == processor -> IntentProcessorType.MIGRATION
|
||||
externalAppIntentProcessors.contains(processor) ||
|
||||
customTabIntentProcessor == processor ||
|
||||
privateCustomTabIntentProcessor == processor -> IntentProcessorType.EXTERNAL_APP
|
||||
|
|
|
@ -15,6 +15,8 @@ import mozilla.components.feature.pwa.intent.WebAppIntentProcessor
|
|||
import mozilla.components.feature.pwa.intent.TrustedWebActivityIntentProcessor
|
||||
import mozilla.components.feature.search.SearchUseCases
|
||||
import mozilla.components.feature.session.SessionUseCases
|
||||
import mozilla.components.support.migration.MigrationIntentProcessor
|
||||
import mozilla.components.support.migration.state.MigrationStore
|
||||
import org.mozilla.fenix.BuildConfig
|
||||
import org.mozilla.fenix.home.intent.FennecBookmarkShortcutsIntentProcessor
|
||||
import org.mozilla.fenix.test.Mockable
|
||||
|
@ -29,7 +31,8 @@ class IntentProcessors(
|
|||
private val sessionUseCases: SessionUseCases,
|
||||
private val searchUseCases: SearchUseCases,
|
||||
private val httpClient: Client,
|
||||
private val customTabsStore: CustomTabsServiceStore
|
||||
private val customTabsStore: CustomTabsServiceStore,
|
||||
private val migrationStore: MigrationStore
|
||||
) {
|
||||
/**
|
||||
* Provides intent processing functionality for ACTION_VIEW and ACTION_SEND intents.
|
||||
|
@ -67,4 +70,8 @@ class IntentProcessors(
|
|||
FennecBookmarkShortcutsIntentProcessor(sessionManager, sessionUseCases.loadUrl)
|
||||
)
|
||||
}
|
||||
|
||||
val migrationIntentProcessor by lazy {
|
||||
MigrationIntentProcessor(migrationStore)
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,77 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?><!-- 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/. -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingTop="55dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingBottom="67dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/migration_firefox_logo"
|
||||
android:layout_width="67dp"
|
||||
android:layout_height="67dp"
|
||||
android:contentDescription="@string/firefox_logo_description"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_firefox" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/migration_welcome_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:maxLines="2"
|
||||
android:text="@string/migration_title"
|
||||
android:textAppearance="@style/HeaderTextStyle"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:textColor="@color/button_text_color"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/migration_description"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/migration_firefox_logo"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Welcome to the all-new Firefox Preview" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/migration_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/migration_description"
|
||||
android:textAppearance="@style/Header16TextStyle"
|
||||
android:textColor="@color/text_scale_example_text_color"
|
||||
android:fontFamily="sans-serif-light"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/migration_firefox_logo" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/migration_status_list"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toTopOf="@+id/migration_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/migration_description"
|
||||
tools:itemCount="5"
|
||||
tools:listitem="@layout/migration_list_item" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/migration_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:enabled="false"
|
||||
android:minWidth="200dp"
|
||||
android:text="@string/migration_updating_app_button_text"
|
||||
android:textAllCaps="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:text="Updating Firefox…" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?><!-- 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/. -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/migration_status_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/migration_icon_description"
|
||||
android:tint="@color/collection_icon_color_violet"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/mozac_ic_check"
|
||||
tools:tint="@color/above_dark_theme" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/migration_item_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/migration_status_image"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@sample/migration_items" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1117,4 +1117,17 @@
|
|||
<string name="search_edit_custom_engine_success_message">Saved %s</string>
|
||||
<!-- Text shown when a user successfully deletes a custom search engine -->
|
||||
<string name="search_delete_search_engine_success_message">Deleted %s</string>
|
||||
|
||||
<!-- Description text for the Firefox brand logo -->
|
||||
<string name="firefox_logo_description">Firefox logo</string>
|
||||
<!-- Title text shown for the migration screen to the new browser -->
|
||||
<string name="migration_title">Welcome to an all-new Firefox</string>
|
||||
<!-- Description text followed by a list of things migrating (e.g. Bookmarks, History) -->
|
||||
<string name="migration_description">A completely redesigned browser awaits, with improved performance and features to help you do more online.\n\nPlease wait while we update Firefox with your</string>
|
||||
<!-- Text on the disabled button while in progress -->
|
||||
<string name="migration_updating_app_button_text">Updating %s…</string>
|
||||
<!-- Text on the enabled button -->
|
||||
<string name="migration_update_app_button">Start %s</string>
|
||||
<!-- Accessibility description text for completed migration item (e.g. Bookmarks, History) -->
|
||||
<string name="migration_icon_description">Migration status: %s</string>
|
||||
</resources>
|
||||
|
|
|
@ -13,6 +13,11 @@
|
|||
android:name="org.mozilla.fenix.MigratingFenixApplication"
|
||||
tools:replace="android:name">
|
||||
<service android:name="org.mozilla.fenix.MigrationService" />
|
||||
|
||||
<activity
|
||||
android:name="org.mozilla.fenix.MigrationProgressActivity"
|
||||
android:exported="false">
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package org.mozilla.fenix
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import mozilla.components.support.migration.FennecMigrator
|
||||
import mozilla.components.support.migration.state.MigrationStore
|
||||
|
@ -47,6 +48,10 @@ class MigratingFenixApplication : FenixApplication() {
|
|||
// The rest of the migrations can happen now.
|
||||
migrationPushSubscriber.start()
|
||||
migrator.startMigrationIfNeeded(migrationStore, MigrationService::class.java)
|
||||
|
||||
// Start migration UI
|
||||
val intent = Intent(this, MigrationProgressActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun migrateBlocking() {
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
/* 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
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.activity_migration.*
|
||||
import kotlinx.android.synthetic.main.migration_list_item.view.*
|
||||
import mozilla.components.support.migration.AbstractMigrationProgressActivity
|
||||
import mozilla.components.support.migration.Migration
|
||||
import mozilla.components.support.migration.Migration.Bookmarks
|
||||
import mozilla.components.support.migration.Migration.History
|
||||
import mozilla.components.support.migration.Migration.Logins
|
||||
import mozilla.components.support.migration.Migration.Settings
|
||||
import mozilla.components.support.migration.MigrationResults
|
||||
import mozilla.components.support.migration.state.MigrationProgress
|
||||
import mozilla.components.support.migration.state.MigrationStore
|
||||
import org.mozilla.fenix.ext.components
|
||||
|
||||
class MigrationProgressActivity : AbstractMigrationProgressActivity() {
|
||||
private val statusAdapter = MigrationStatusAdapter()
|
||||
override val store: MigrationStore by lazy { components.migrationStore }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setContentView(R.layout.activity_migration)
|
||||
init()
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
fun init() {
|
||||
migration_status_list.apply {
|
||||
layoutManager = LinearLayoutManager(this@MigrationProgressActivity)
|
||||
adapter = statusAdapter
|
||||
}
|
||||
|
||||
migration_button.apply {
|
||||
setOnClickListener {
|
||||
finish()
|
||||
overridePendingTransition(0, 0)
|
||||
|
||||
// If we received a user-initiated intent, throw this back to the intent receiver.
|
||||
if (intent.hasExtra(HomeActivity.OPEN_TO_BROWSER)) {
|
||||
intent.setClassName(applicationContext, IntentReceiverActivity::class.java.name)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
text = getString(
|
||||
R.string.migration_updating_app_button_text,
|
||||
getString(R.string.app_name)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
if (hasFocus) {
|
||||
// Enables sticky immersive mode.
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMigrationCompleted() {
|
||||
// Enable clicking the finish button
|
||||
migration_button.apply {
|
||||
isEnabled = true
|
||||
text = getString(R.string.migration_update_app_button, getString(R.string.app_name))
|
||||
setBackgroundColor(ContextCompat.getColor(context, R.color.button_text_color))
|
||||
setTextColor(ContextCompat.getColor(context, R.color.white_color))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMigrationStateChanged(progress: MigrationProgress, results: MigrationResults) {
|
||||
statusAdapter.submitList(results.toItemList())
|
||||
}
|
||||
}
|
||||
|
||||
// These are the only items we want to show migrating in the UI.
|
||||
internal val whiteList = mapOf(
|
||||
Bookmarks to R.string.preferences_sync_bookmarks,
|
||||
History to R.string.preferences_sync_history,
|
||||
Logins to R.string.preferences_sync_logins,
|
||||
Settings to R.string.settings_title
|
||||
)
|
||||
|
||||
internal fun MigrationResults.toItemList() = filterKeys {
|
||||
whiteList.keys.contains(it)
|
||||
}.map { (type, status) ->
|
||||
MigrationItem(
|
||||
type,
|
||||
status.success
|
||||
)
|
||||
}
|
||||
|
||||
internal data class MigrationItem(val migration: Migration, val status: Boolean)
|
||||
|
||||
internal class MigrationStatusAdapter :
|
||||
ListAdapter<MigrationItem, MigrationStatusAdapter.ViewHolder>(DiffCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int = R.layout.migration_list_item
|
||||
|
||||
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
private val context = view.context
|
||||
private val title = view.migration_item_name
|
||||
private val status = view.migration_status_image
|
||||
|
||||
fun bind(item: MigrationItem) {
|
||||
// Get the resource ID for the item.
|
||||
val migrationText = whiteList[item.migration]?.run {
|
||||
context.getString(this)
|
||||
} ?: ""
|
||||
title.text = migrationText
|
||||
status.visibility = if (item.status) View.VISIBLE else View.INVISIBLE
|
||||
status.contentDescription =
|
||||
context.getString(R.string.migration_icon_description, migrationText)
|
||||
}
|
||||
}
|
||||
|
||||
private object DiffCallback : DiffUtil.ItemCallback<MigrationItem>() {
|
||||
|
||||
override fun areItemsTheSame(oldItem: MigrationItem, newItem: MigrationItem) =
|
||||
oldItem.migration.javaClass.simpleName == newItem.migration.javaClass.simpleName
|
||||
|
||||
override fun areContentsTheSame(oldItem: MigrationItem, newItem: MigrationItem) =
|
||||
oldItem.migration.javaClass.simpleName == newItem.migration.javaClass.simpleName &&
|
||||
oldItem.status == newItem.status
|
||||
}
|
||||
}
|
|
@ -6,11 +6,12 @@ package org.mozilla.fenix
|
|||
|
||||
import mozilla.components.support.migration.AbstractMigrationService
|
||||
import mozilla.components.support.migration.state.MigrationStore
|
||||
import org.mozilla.fenix.ext.components
|
||||
|
||||
/**
|
||||
* Background service for running the migration from legacy Firefox for Android (Fennec).
|
||||
*/
|
||||
class MigrationService : AbstractMigrationService() {
|
||||
override val migrator by lazy { getMigratorFromApplication() }
|
||||
override val store: MigrationStore by lazy { getMigrationStoreFromApplication() }
|
||||
override val store: MigrationStore by lazy { components.migrationStore }
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue