From 051f09a15a04200ead71b04b0a0aec4ae3ef9cea Mon Sep 17 00:00:00 2001 From: Mugurell Date: Mon, 26 Aug 2019 18:18:33 +0300 Subject: [PATCH] [fenix] For https://github.com/mozilla-mobile/fenix/issues/4007 - ShareFragment will set the contained Views' state ShareFragment which acts as a container would contain all business logic needed for populating it's Views. Data initialization should be done only once since the app state has no reason to change after the ShareFragment is created and is done as soon as possible, in onAttach(). Because of the expected short lifespan of this fragment, given the fact that the state has no reason to change and we handle orientation changes ourselves to keep things simple I didn't use a ViewModel to persist the state. --- .../org/mozilla/fenix/share/ShareFragment.kt | 106 +++++++++++++++++- .../mozilla/fenix/share/ShareInteractor.kt | 4 +- .../fenix/share/ShareToAccountDevicesView.kt | 11 ++ .../mozilla/fenix/share/ShareToAppsView.kt | 20 +++- 4 files changed, 133 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt b/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt index 3c51798a5b..c679bbba65 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt @@ -4,16 +4,33 @@ package org.mozilla.fenix.share +import android.content.Context +import android.content.Intent +import android.content.Intent.ACTION_SEND +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.content.pm.ResolveInfo import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatDialogFragment +import androidx.lifecycle.lifecycleScope import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_share.view.* +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import mozilla.components.concept.sync.DeviceCapability +import mozilla.components.concept.sync.DeviceType +import mozilla.components.service.fxa.manager.FxaAccountManager import org.mozilla.fenix.R +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.share.listadapters.AppShareOption +import org.mozilla.fenix.share.listadapters.SyncShareOption +@Suppress("TooManyFunctions") class ShareFragment : AppCompatDialogFragment() { interface TabsSharedCallback { fun onTabsShared(tabsSize: Int) @@ -23,7 +40,27 @@ class ShareFragment : AppCompatDialogFragment() { private lateinit var shareCloseView: ShareCloseView private lateinit var shareToAccountDevicesView: ShareToAccountDevicesView private lateinit var shareToAppsView: ShareToAppsView - private var tabs: Array = emptyArray() + private lateinit var appsListDeferred: Deferred> + private lateinit var devicesListDeferred: Deferred> + + override fun onAttach(context: Context) { + super.onAttach(context) + + // Start preparing the data as soon as we have a valid Context + appsListDeferred = lifecycleScope.async(Dispatchers.IO) { + val shareIntent = Intent(ACTION_SEND).apply { + type = "text/plain" + flags = FLAG_ACTIVITY_NEW_TASK + } + val shareAppsActivities = getIntentActivities(shareIntent, context) + buildAppsList(shareAppsActivities, context) + } + + devicesListDeferred = lifecycleScope.async(Dispatchers.IO) { + val fxaAccountManager = context.components.backgroundServices.accountManager + buildDeviceList(fxaAccountManager) + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -41,16 +78,79 @@ class ShareFragment : AppCompatDialogFragment() { throw IllegalStateException("URL and tabs cannot both be null.") } - tabs = args.tabs ?: arrayOf(ShareTab(args.url!!, args.title ?: "")) + val tabs = args.tabs?.toList() ?: listOf(ShareTab(args.url!!, args.title ?: "")) shareInteractor = ShareInteractor() + if (isSharingToDevicesAvailable(requireContext().applicationContext)) { + shareToAccountDevicesView = ShareToAccountDevicesView(view.devicesShareLayout, shareInteractor) + } else { + view.devicesShareGroup.visibility = View.GONE + } shareCloseView = ShareCloseView(view.closeSharingLayout, shareInteractor) - shareToAccountDevicesView = ShareToAccountDevicesView(view.devicesShareLayout, shareInteractor) shareToAppsView = ShareToAppsView(view.appsShareLayout, shareInteractor) return view } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + lifecycleScope.launch { + val devicesShareOptions = devicesListDeferred.await() + shareToAccountDevicesView.setSharetargets(devicesShareOptions) + val appsToShareTo = appsListDeferred.await() + shareToAppsView.setSharetargets(appsToShareTo) + } + } + + private fun isSharingToDevicesAvailable(context: Context) = + !context.components.backgroundServices.accountManager.accountNeedsReauth() + + private fun getIntentActivities(shareIntent: Intent, context: Context): List? { + return context.packageManager.queryIntentActivities(shareIntent, 0) + } + + private fun buildAppsList(intentActivities: List?, context: Context): List { + return intentActivities?.map { resolveInfo -> + AppShareOption( + resolveInfo.loadLabel(context.packageManager).toString(), + resolveInfo.loadIcon(context.packageManager), + resolveInfo.activityInfo.packageName, + resolveInfo.activityInfo.name + ) + } ?: emptyList() + } + + private fun buildDeviceList(accountManager: FxaAccountManager): List { + val list = mutableListOf() + + if (accountManager.authenticatedAccount() == null) { + list.add(SyncShareOption.SignIn) + return list + } + + accountManager.authenticatedAccount()?.deviceConstellation()?.state()?.otherDevices?.let { devices -> + val shareableDevices = devices.filter { it.capabilities.contains(DeviceCapability.SEND_TAB) } + + if (shareableDevices.isEmpty()) { + list.add(SyncShareOption.AddNewDevice) + } + + val shareOptions = shareableDevices.map { + when (it.deviceType) { + DeviceType.MOBILE -> SyncShareOption.Mobile(it.displayName, it) + else -> SyncShareOption.Desktop(it.displayName, it) + } + } + list.addAll(shareOptions) + + if (shareableDevices.size > 1) { + list.add(SyncShareOption.SendAll(shareableDevices)) + } + } + return list + } } @Parcelize diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareInteractor.kt b/app/src/main/java/org/mozilla/fenix/share/ShareInteractor.kt index 8eebc74b25..d80e3a3b6f 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareInteractor.kt @@ -5,7 +5,7 @@ package org.mozilla.fenix.share import mozilla.components.concept.sync.Device -import org.mozilla.fenix.share.listadapters.Application +import org.mozilla.fenix.share.listadapters.AppShareOption /** * Interactor for the share screen. @@ -31,7 +31,7 @@ class ShareInteractor : ShareCloseInteractor, ShareToAccountDevicesInteractor, S TODO("not yet!? implemented") } - override fun onShareToApp(appToShareTo: Application) { + override fun onShareToApp(appToShareTo: AppShareOption) { TODO("not yet!? implemented") } } diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareToAccountDevicesView.kt b/app/src/main/java/org/mozilla/fenix/share/ShareToAccountDevicesView.kt index bdd5109583..9be59e389e 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareToAccountDevicesView.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareToAccountDevicesView.kt @@ -7,8 +7,11 @@ package org.mozilla.fenix.share import android.view.LayoutInflater import android.view.ViewGroup import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.share_to_account_devices.* import mozilla.components.concept.sync.Device import org.mozilla.fenix.R +import org.mozilla.fenix.share.listadapters.AccountDevicesShareAdapter +import org.mozilla.fenix.share.listadapters.SyncShareOption /** * Callbacks for possible user interactions on the [ShareToAccountDevicesView] @@ -27,5 +30,13 @@ class ShareToAccountDevicesView( init { LayoutInflater.from(containerView.context) .inflate(R.layout.share_to_account_devices, containerView, true) + + devicesList.adapter = AccountDevicesShareAdapter(interactor) + } + + fun setSharetargets(targets: List) { + with(devicesList.adapter as AccountDevicesShareAdapter) { + updateData(targets) + } } } diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareToAppsView.kt b/app/src/main/java/org/mozilla/fenix/share/ShareToAppsView.kt index 39aeb1f587..84bf95d3ce 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareToAppsView.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareToAppsView.kt @@ -5,24 +5,38 @@ package org.mozilla.fenix.share import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import kotlinx.android.extensions.LayoutContainer import org.mozilla.fenix.R -import org.mozilla.fenix.share.listadapters.Application +import org.mozilla.fenix.share.listadapters.AppShareOption +import kotlinx.android.synthetic.main.share_to_apps.* +import org.mozilla.fenix.share.listadapters.AppShareAdapter /** * Callbacks for possible user interactions on the [ShareCloseView] */ interface ShareToAppsInteractor { - fun onShareToApp(appToShareTo: Application) + fun onShareToApp(appToShareTo: AppShareOption) } class ShareToAppsView( override val containerView: ViewGroup, - private val interactor: ShareToAppsInteractor + interactor: ShareToAppsInteractor ) : LayoutContainer { init { LayoutInflater.from(containerView.context) .inflate(R.layout.share_to_apps, containerView, true) + + appsList.adapter = AppShareAdapter(interactor) + } + + fun setSharetargets(targets: List) { + progressBar.visibility = View.GONE + appsList.visibility = View.VISIBLE + + with(appsList.adapter as AppShareAdapter) { + updateData(targets) + } } }