For #4231
Added kapt plugin + dependencies in order to be able to use Room Added recent apps to share fragment (top 6) Extracted dimens of share_to_apps.xml in the dimens filemaster
parent
3486f30b6a
commit
fdbf63fb97
|
@ -5,6 +5,7 @@ plugins {
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'jacoco'
|
||||
apply from: "$project.rootDir/automation/gradle/versionCode.gradle"
|
||||
apply plugin: 'androidx.navigation.safeargs.kotlin'
|
||||
|
@ -436,6 +437,7 @@ dependencies {
|
|||
implementation Deps.mozilla_feature_readerview
|
||||
implementation Deps.mozilla_feature_tab_collections
|
||||
implementation Deps.mozilla_feature_top_sites
|
||||
implementation Deps.mozilla_feature_share
|
||||
implementation Deps.mozilla_feature_accounts_push
|
||||
implementation Deps.mozilla_feature_webcompat
|
||||
implementation Deps.mozilla_feature_webnotifications
|
||||
|
|
|
@ -293,7 +293,7 @@ private fun assertSendToDeviceTitle() = SendToDeviceTitle()
|
|||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||
|
||||
private fun ShareALinkTitle() =
|
||||
onView(allOf(withText("SHARE A LINK"), withResourceName("link_header")))
|
||||
onView(allOf(withText(R.string.share_link_all_apps_subheader), withResourceName("apps_link_header")))
|
||||
|
||||
private fun assertShareALinkTitle() = ShareALinkTitle()
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import android.net.Uri
|
|||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.navigation.NavController
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
|
@ -22,6 +23,7 @@ import mozilla.components.concept.engine.prompt.ShareData
|
|||
import mozilla.components.concept.sync.Device
|
||||
import mozilla.components.concept.sync.TabData
|
||||
import mozilla.components.feature.accounts.push.SendTabUseCases
|
||||
import mozilla.components.feature.share.RecentAppsStorage
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.FenixSnackbar
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
|
@ -65,6 +67,8 @@ class DefaultShareController(
|
|||
private val sendTabUseCases: SendTabUseCases,
|
||||
private val snackbar: FenixSnackbar,
|
||||
private val navController: NavController,
|
||||
private val recentAppsStorage: RecentAppsStorage,
|
||||
private val lifecycleScope: CoroutineScope,
|
||||
private val dismiss: (ShareController.Result) -> Unit
|
||||
) : ShareController {
|
||||
|
||||
|
@ -79,6 +83,10 @@ class DefaultShareController(
|
|||
}
|
||||
|
||||
override fun handleShareToApp(app: AppShareOption) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
recentAppsStorage.updateRecentApp(app.packageName)
|
||||
}
|
||||
|
||||
val intent = Intent(ACTION_SEND).apply {
|
||||
putExtra(EXTRA_TEXT, getShareText())
|
||||
putExtra(EXTRA_SUBJECT, shareData.map { it.title }.joinToString(", "))
|
||||
|
|
|
@ -12,14 +12,20 @@ import android.view.ViewGroup
|
|||
import androidx.appcompat.app.AppCompatDialogFragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.observe
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import kotlinx.android.synthetic.main.fragment_share.view.*
|
||||
import kotlinx.android.synthetic.main.fragment_share.view.appsShareLayout
|
||||
import kotlinx.android.synthetic.main.fragment_share.view.closeSharingContent
|
||||
import kotlinx.android.synthetic.main.fragment_share.view.closeSharingScrim
|
||||
import kotlinx.android.synthetic.main.fragment_share.view.devicesShareLayout
|
||||
import kotlinx.android.synthetic.main.fragment_share.view.shareWrapper
|
||||
import mozilla.components.browser.state.action.ContentAction
|
||||
import mozilla.components.browser.state.selector.findTabOrCustomTab
|
||||
import mozilla.components.concept.engine.prompt.PromptRequest
|
||||
import mozilla.components.feature.accounts.push.SendTabUseCases
|
||||
import mozilla.components.feature.share.RecentAppsStorage
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.FenixSnackbar
|
||||
import org.mozilla.fenix.ext.getRootView
|
||||
|
@ -67,7 +73,9 @@ class ShareFragment : AppCompatDialogFragment() {
|
|||
shareData = shareData,
|
||||
snackbar = FenixSnackbar.makeWithToolbarPadding(requireActivity().getRootView()!!),
|
||||
navController = findNavController(),
|
||||
sendTabUseCases = SendTabUseCases(accountManager)
|
||||
sendTabUseCases = SendTabUseCases(accountManager),
|
||||
recentAppsStorage = RecentAppsStorage(requireContext()),
|
||||
lifecycleScope = lifecycleScope
|
||||
) { result ->
|
||||
consumePrompt {
|
||||
when (result) {
|
||||
|
@ -108,6 +116,9 @@ class ShareFragment : AppCompatDialogFragment() {
|
|||
viewModel.appsList.observe(viewLifecycleOwner) { appsToShareTo ->
|
||||
shareToAppsView.setShareTargets(appsToShareTo)
|
||||
}
|
||||
viewModel.recentAppsList.observe(viewLifecycleOwner) { appsToShareTo ->
|
||||
shareToAppsView.setRecentShareTargets(appsToShareTo)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,7 +8,10 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.share_to_apps.*
|
||||
import kotlinx.android.synthetic.main.share_to_apps.appsList
|
||||
import kotlinx.android.synthetic.main.share_to_apps.progressBar
|
||||
import kotlinx.android.synthetic.main.share_to_apps.recentAppsContainer
|
||||
import kotlinx.android.synthetic.main.share_to_apps.recentAppsList
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.share.listadapters.AppShareAdapter
|
||||
import org.mozilla.fenix.share.listadapters.AppShareOption
|
||||
|
@ -26,18 +29,30 @@ class ShareToAppsView(
|
|||
) : LayoutContainer {
|
||||
|
||||
private val adapter = AppShareAdapter(interactor)
|
||||
private val recentAdapter = AppShareAdapter(interactor)
|
||||
|
||||
init {
|
||||
LayoutInflater.from(containerView.context)
|
||||
.inflate(R.layout.share_to_apps, containerView, true)
|
||||
|
||||
appsList.adapter = adapter
|
||||
recentAppsList.adapter = recentAdapter
|
||||
}
|
||||
|
||||
fun setShareTargets(targets: List<AppShareOption>) {
|
||||
progressBar.visibility = View.GONE
|
||||
appsList.visibility = View.VISIBLE
|
||||
|
||||
appsList.visibility = View.VISIBLE
|
||||
adapter.submitList(targets)
|
||||
}
|
||||
|
||||
fun setRecentShareTargets(recentTargets: List<AppShareOption>) {
|
||||
if (recentTargets.isEmpty()) {
|
||||
return
|
||||
}
|
||||
progressBar.visibility = View.GONE
|
||||
|
||||
recentAppsContainer.visibility = View.VISIBLE
|
||||
recentAdapter.submitList(recentTargets)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import androidx.lifecycle.viewModelScope
|
|||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.concept.sync.DeviceCapability
|
||||
import mozilla.components.feature.share.RecentAppsStorage
|
||||
import mozilla.components.service.fxa.manager.FxaAccountManager
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.isOnline
|
||||
|
@ -29,11 +30,17 @@ import org.mozilla.fenix.share.listadapters.SyncShareOption
|
|||
|
||||
class ShareViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
companion object {
|
||||
private const val RECENT_APPS_LIMIT = 6
|
||||
}
|
||||
|
||||
private val connectivityManager by lazy { application.getSystemService<ConnectivityManager>() }
|
||||
private val fxaAccountManager = application.components.backgroundServices.accountManager
|
||||
private val recentAppsStorage = RecentAppsStorage(application.applicationContext)
|
||||
|
||||
private val devicesListLiveData = MutableLiveData<List<SyncShareOption>>(emptyList())
|
||||
private val appsListLiveData = MutableLiveData<List<AppShareOption>>(emptyList())
|
||||
private val recentAppsListLiveData = MutableLiveData<List<AppShareOption>>(emptyList())
|
||||
|
||||
@VisibleForTesting
|
||||
internal val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
|
@ -61,6 +68,10 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) {
|
|||
* List of applications that can be shared to.
|
||||
*/
|
||||
val appsList: LiveData<List<AppShareOption>> get() = appsListLiveData
|
||||
/**
|
||||
* List of recent applications that can be shared to.
|
||||
*/
|
||||
val recentAppsList: LiveData<List<AppShareOption>> get() = recentAppsListLiveData
|
||||
|
||||
/**
|
||||
* Load a list of devices and apps into [devicesList] and [appsList].
|
||||
|
@ -77,7 +88,12 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) {
|
|||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
val shareAppsActivities = getIntentActivities(shareIntent, getApplication())
|
||||
val apps = buildAppsList(shareAppsActivities, getApplication())
|
||||
var apps = buildAppsList(shareAppsActivities, getApplication())
|
||||
recentAppsStorage.updateDatabaseWithNewApps(apps.map { app -> app.packageName })
|
||||
val recentApps = buildRecentAppsList(apps)
|
||||
apps = filterOutRecentApps(apps, recentApps)
|
||||
|
||||
recentAppsListLiveData.postValue(recentApps)
|
||||
appsListLiveData.postValue(apps)
|
||||
}
|
||||
|
||||
|
@ -87,6 +103,27 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun filterOutRecentApps(
|
||||
apps: List<AppShareOption>,
|
||||
recentApps: List<AppShareOption>
|
||||
): List<AppShareOption> {
|
||||
return apps.filter { app -> !recentApps.contains(app) }
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
internal fun buildRecentAppsList(apps: List<AppShareOption>): List<AppShareOption> {
|
||||
val recentAppsDatabase = recentAppsStorage.getRecentAppsUpTo(RECENT_APPS_LIMIT)
|
||||
val result: MutableList<AppShareOption> = ArrayList()
|
||||
for (recentApp in recentAppsDatabase) {
|
||||
for (app in apps) {
|
||||
if (recentApp.packageName == app.packageName) {
|
||||
result.add(app)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the network callback and cleans up.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- 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/. -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?inset"/>
|
||||
<corners android:radius="@dimen/share_recent_apps_background_radius"/>
|
||||
</shape>
|
|
@ -1,10 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- 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"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
@ -13,43 +11,75 @@
|
|||
android:id="@+id/progressBar"
|
||||
android:layout_width="76dp"
|
||||
android:layout_height="37dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginTop="@dimen/share_progress_bar_margin"
|
||||
android:layout_marginBottom="@dimen/share_progress_bar_margin"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/link_header" />
|
||||
app:layout_constraintTop_toBottomOf="@id/apps_link_header" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/link_header"
|
||||
<LinearLayout
|
||||
android:id="@+id/recentAppsContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/share_link_subheader"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="?secondaryText"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginStart="@dimen/share_recent_apps_margin"
|
||||
android:layout_marginTop="@dimen/share_recent_apps_margin"
|
||||
android:layout_marginBottom="@dimen/share_recent_apps_margin"
|
||||
android:background="@drawable/recent_apps_background"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/share_recent_apps_padding"
|
||||
android:paddingTop="@dimen/share_recent_apps_padding"
|
||||
android:paddingEnd="@dimen/share_recent_apps_padding"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recent_apps_link_header"
|
||||
style="@style/ShareHeaderTextStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/share_link_recent_apps_subheader" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recentAppsList"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:clipToPadding="false"
|
||||
android:minHeight="@dimen/share_list_min_height"
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="2" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/apps_link_header"
|
||||
style="@style/ShareHeaderTextStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/share_all_apps_header_margin"
|
||||
android:layout_marginTop="@dimen/share_all_apps_header_margin"
|
||||
android:text="@string/share_link_all_apps_subheader"
|
||||
app:layout_constraintStart_toEndOf="@+id/recentAppsContainer"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/appsList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="160dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginBottom="@dimen/share_all_apps_list_margin"
|
||||
android:clipToPadding="false"
|
||||
android:minHeight="@dimen/share_list_min_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingStart="@dimen/share_all_apps_list_padding_start"
|
||||
android:paddingEnd="@dimen/share_all_apps_list_padding_end"
|
||||
android:visibility="gone"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/link_header"
|
||||
app:layout_constraintStart_toEndOf="@+id/recentAppsContainer"
|
||||
app:layout_constraintTop_toBottomOf="@id/apps_link_header"
|
||||
app:spanCount="2" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -115,4 +115,16 @@
|
|||
<dimen name="migration_progress_margin_vertical">8dp</dimen>
|
||||
<dimen name="migration_progress_margin_start">4dp</dimen>
|
||||
|
||||
<!-- Share Fragment -->
|
||||
<dimen name="share_recent_apps_background_radius">10dp</dimen>
|
||||
<dimen name="share_recent_apps_padding">8dp</dimen>
|
||||
<dimen name="share_recent_apps_margin">8dp</dimen>
|
||||
<dimen name="share_all_apps_header_margin">16dp</dimen>
|
||||
<dimen name="share_list_min_height">160dp</dimen>
|
||||
<dimen name="share_all_apps_list_margin">8dp</dimen>
|
||||
<dimen name="share_all_apps_list_padding_start">16dp</dimen>
|
||||
<dimen name="share_all_apps_list_padding_end">8dp</dimen>
|
||||
<dimen name="share_header_text_size">12sp</dimen>
|
||||
<dimen name="share_progress_bar_margin">16dp</dimen>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -361,7 +361,14 @@
|
|||
<item name="android:minHeight">40dp</item>
|
||||
<item name="android:layout_marginTop">32dp</item>
|
||||
<item name="android:textColor">?accentUsedOnDarkBackground</item>
|
||||
</style>
|
||||
|
||||
<style name="ShareHeaderTextStyle">
|
||||
<item name="android:singleLine">true</item>
|
||||
<item name="android:textAllCaps">true</item>
|
||||
<item name="android:textColor">?secondaryText</item>
|
||||
<item name="android:textSize">@dimen/share_header_text_size</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="FirefoxAccountsDialogStyle" parent="DialogStyleBase">
|
||||
|
|
|
@ -13,9 +13,9 @@ import android.net.ConnectivityManager
|
|||
import androidx.core.content.getSystemService
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import io.mockk.mockkStatic
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import mozilla.components.service.fxa.manager.FxaAccountManager
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
|
|
|
@ -126,6 +126,7 @@ object Deps {
|
|||
const val mozilla_feature_tab_collections = "org.mozilla.components:feature-tab-collections:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_accounts_push = "org.mozilla.components:feature-accounts-push:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_top_sites = "org.mozilla.components:feature-top-sites:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_share = "org.mozilla.components:feature-share:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_webcompat = "org.mozilla.components:feature-webcompat:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_webnotifications = "org.mozilla.components:feature-webnotifications:${Versions.mozilla_android_components}"
|
||||
|
||||
|
|
Loading…
Reference in New Issue