For #2897 - Add Action States To Share Sheet
parent
2e2bac4ccd
commit
9a4610f068
|
@ -28,6 +28,7 @@ import org.mozilla.fenix.share.listadapters.AppShareOption
|
||||||
* Delegated by View Interactors, handles container business logic and operates changes on it.
|
* Delegated by View Interactors, handles container business logic and operates changes on it.
|
||||||
*/
|
*/
|
||||||
interface ShareController {
|
interface ShareController {
|
||||||
|
fun handleReauth()
|
||||||
fun handleShareClosed()
|
fun handleShareClosed()
|
||||||
fun handleShareToApp(app: AppShareOption)
|
fun handleShareToApp(app: AppShareOption)
|
||||||
fun handleAddNewDevice()
|
fun handleAddNewDevice()
|
||||||
|
@ -53,6 +54,12 @@ class DefaultShareController(
|
||||||
private val navController: NavController,
|
private val navController: NavController,
|
||||||
private val dismiss: () -> Unit
|
private val dismiss: () -> Unit
|
||||||
) : ShareController {
|
) : ShareController {
|
||||||
|
override fun handleReauth() {
|
||||||
|
val directions = ShareFragmentDirections.actionShareFragmentToAccountProblemFragment()
|
||||||
|
navController.nav(R.id.shareFragment, directions)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
override fun handleShareClosed() {
|
override fun handleShareClosed() {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@ import android.content.Intent
|
||||||
import android.content.Intent.ACTION_SEND
|
import android.content.Intent.ACTION_SEND
|
||||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
import android.content.pm.ResolveInfo
|
import android.content.pm.ResolveInfo
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.Network
|
||||||
|
import android.net.NetworkRequest
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
@ -45,10 +48,16 @@ class ShareFragment : AppCompatDialogFragment() {
|
||||||
private lateinit var shareToAppsView: ShareToAppsView
|
private lateinit var shareToAppsView: ShareToAppsView
|
||||||
private lateinit var appsListDeferred: Deferred<List<AppShareOption>>
|
private lateinit var appsListDeferred: Deferred<List<AppShareOption>>
|
||||||
private lateinit var devicesListDeferred: Deferred<List<SyncShareOption>>
|
private lateinit var devicesListDeferred: Deferred<List<SyncShareOption>>
|
||||||
|
private var connectivityManager: ConnectivityManager? = null
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
|
|
||||||
|
connectivityManager =
|
||||||
|
context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
|
||||||
|
val networkRequest = NetworkRequest.Builder().build()
|
||||||
|
connectivityManager?.registerNetworkCallback(networkRequest, networkCallback)
|
||||||
|
|
||||||
// Start preparing the data as soon as we have a valid Context
|
// Start preparing the data as soon as we have a valid Context
|
||||||
appsListDeferred = lifecycleScope.async(Dispatchers.IO) {
|
appsListDeferred = lifecycleScope.async(Dispatchers.IO) {
|
||||||
val shareIntent = Intent(ACTION_SEND).apply {
|
val shareIntent = Intent(ACTION_SEND).apply {
|
||||||
|
@ -70,6 +79,35 @@ class ShareFragment : AppCompatDialogFragment() {
|
||||||
setStyle(STYLE_NO_TITLE, R.style.ShareDialogStyle)
|
setStyle(STYLE_NO_TITLE, R.style.ShareDialogStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||||
|
override fun onLost(network: Network?) {
|
||||||
|
reloadDevices()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAvailable(network: Network?) {
|
||||||
|
reloadDevices()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reloadDevices() {
|
||||||
|
context?.let {
|
||||||
|
val fxaAccountManager = it.components.backgroundServices.accountManager
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val refreshDevicesAsync =
|
||||||
|
fxaAccountManager.authenticatedAccount()?.deviceConstellation()
|
||||||
|
?.refreshDevicesAsync()
|
||||||
|
refreshDevicesAsync?.await()
|
||||||
|
val devicesShareOptions = buildDeviceList(fxaAccountManager)
|
||||||
|
shareToAccountDevicesView.setSharetargets(devicesShareOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
connectivityManager?.unregisterNetworkCallback(networkCallback)
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
@ -95,11 +133,8 @@ class ShareFragment : AppCompatDialogFragment() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isSharingToDevicesAvailable(requireContext().applicationContext)) {
|
shareToAccountDevicesView =
|
||||||
shareToAccountDevicesView = ShareToAccountDevicesView(view.devicesShareLayout, shareInteractor)
|
ShareToAccountDevicesView(view.devicesShareLayout, shareInteractor)
|
||||||
} else {
|
|
||||||
view.devicesShareGroup.visibility = View.GONE
|
|
||||||
}
|
|
||||||
shareCloseView = ShareCloseView(view.closeSharingLayout, shareInteractor)
|
shareCloseView = ShareCloseView(view.closeSharingLayout, shareInteractor)
|
||||||
shareToAppsView = ShareToAppsView(view.appsShareLayout, shareInteractor)
|
shareToAppsView = ShareToAppsView(view.appsShareLayout, shareInteractor)
|
||||||
|
|
||||||
|
@ -117,14 +152,14 @@ class ShareFragment : AppCompatDialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isSharingToDevicesAvailable(context: Context) =
|
|
||||||
!context.components.backgroundServices.accountManager.accountNeedsReauth()
|
|
||||||
|
|
||||||
private fun getIntentActivities(shareIntent: Intent, context: Context): List<ResolveInfo>? {
|
private fun getIntentActivities(shareIntent: Intent, context: Context): List<ResolveInfo>? {
|
||||||
return context.packageManager.queryIntentActivities(shareIntent, 0)
|
return context.packageManager.queryIntentActivities(shareIntent, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildAppsList(intentActivities: List<ResolveInfo>?, context: Context): List<AppShareOption> {
|
private fun buildAppsList(
|
||||||
|
intentActivities: List<ResolveInfo>?,
|
||||||
|
context: Context
|
||||||
|
): List<AppShareOption> {
|
||||||
return intentActivities?.map { resolveInfo ->
|
return intentActivities?.map { resolveInfo ->
|
||||||
AppShareOption(
|
AppShareOption(
|
||||||
resolveInfo.loadLabel(context.packageManager).toString(),
|
resolveInfo.loadLabel(context.packageManager).toString(),
|
||||||
|
@ -135,16 +170,30 @@ class ShareFragment : AppCompatDialogFragment() {
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("ReturnCount")
|
||||||
private fun buildDeviceList(accountManager: FxaAccountManager): List<SyncShareOption> {
|
private fun buildDeviceList(accountManager: FxaAccountManager): List<SyncShareOption> {
|
||||||
val list = mutableListOf<SyncShareOption>()
|
val list = mutableListOf<SyncShareOption>()
|
||||||
|
|
||||||
|
val activeNetwork = connectivityManager?.activeNetworkInfo
|
||||||
|
if (activeNetwork?.isConnected != true) {
|
||||||
|
list.add(SyncShareOption.Offline)
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
if (accountManager.authenticatedAccount() == null) {
|
if (accountManager.authenticatedAccount() == null) {
|
||||||
list.add(SyncShareOption.SignIn)
|
list.add(SyncShareOption.SignIn)
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
accountManager.authenticatedAccount()?.deviceConstellation()?.state()?.otherDevices?.let { devices ->
|
if (accountManager.accountNeedsReauth()) {
|
||||||
val shareableDevices = devices.filter { it.capabilities.contains(DeviceCapability.SEND_TAB) }
|
list.add(SyncShareOption.Reconnect)
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
accountManager.authenticatedAccount()?.deviceConstellation()?.state()
|
||||||
|
?.otherDevices?.let { devices ->
|
||||||
|
val shareableDevices =
|
||||||
|
devices.filter { it.capabilities.contains(DeviceCapability.SEND_TAB) }
|
||||||
|
|
||||||
if (shareableDevices.isEmpty()) {
|
if (shareableDevices.isEmpty()) {
|
||||||
list.add(SyncShareOption.AddNewDevice)
|
list.add(SyncShareOption.AddNewDevice)
|
||||||
|
|
|
@ -13,6 +13,10 @@ import org.mozilla.fenix.share.listadapters.AppShareOption
|
||||||
class ShareInteractor(
|
class ShareInteractor(
|
||||||
private val controller: ShareController
|
private val controller: ShareController
|
||||||
) : ShareCloseInteractor, ShareToAccountDevicesInteractor, ShareToAppsInteractor {
|
) : ShareCloseInteractor, ShareToAccountDevicesInteractor, ShareToAppsInteractor {
|
||||||
|
override fun onReauth() {
|
||||||
|
controller.handleReauth()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onShareClosed() {
|
override fun onShareClosed() {
|
||||||
controller.handleShareClosed()
|
controller.handleShareClosed()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.mozilla.fenix.share.listadapters.SyncShareOption
|
||||||
*/
|
*/
|
||||||
interface ShareToAccountDevicesInteractor {
|
interface ShareToAccountDevicesInteractor {
|
||||||
fun onSignIn()
|
fun onSignIn()
|
||||||
|
fun onReauth()
|
||||||
fun onAddNewDevice()
|
fun onAddNewDevice()
|
||||||
fun onShareToDevice(device: Device)
|
fun onShareToDevice(device: Device)
|
||||||
fun onShareToAllDevices(devices: List<Device>)
|
fun onShareToAllDevices(devices: List<Device>)
|
||||||
|
|
|
@ -37,6 +37,8 @@ class AccountDevicesShareAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class SyncShareOption {
|
sealed class SyncShareOption {
|
||||||
|
object Reconnect : SyncShareOption()
|
||||||
|
object Offline : SyncShareOption()
|
||||||
object SignIn : SyncShareOption()
|
object SignIn : SyncShareOption()
|
||||||
object AddNewDevice : SyncShareOption()
|
object AddNewDevice : SyncShareOption()
|
||||||
data class SendAll(val devices: List<Device>) : SyncShareOption()
|
data class SendAll(val devices: List<Device>) : SyncShareOption()
|
||||||
|
|
|
@ -37,6 +37,10 @@ class AccountDeviceViewHolder(
|
||||||
is SyncShareOption.SendAll -> interactor.onShareToAllDevices(option.devices)
|
is SyncShareOption.SendAll -> interactor.onShareToAllDevices(option.devices)
|
||||||
is SyncShareOption.Mobile -> interactor.onShareToDevice(option.device)
|
is SyncShareOption.Mobile -> interactor.onShareToDevice(option.device)
|
||||||
is SyncShareOption.Desktop -> interactor.onShareToDevice(option.device)
|
is SyncShareOption.Desktop -> interactor.onShareToDevice(option.device)
|
||||||
|
SyncShareOption.Reconnect -> interactor.onReauth()
|
||||||
|
SyncShareOption.Offline -> {
|
||||||
|
// nothing we are offline
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +52,16 @@ class AccountDeviceViewHolder(
|
||||||
R.drawable.mozac_ic_sync,
|
R.drawable.mozac_ic_sync,
|
||||||
R.color.default_share_background
|
R.color.default_share_background
|
||||||
)
|
)
|
||||||
|
SyncShareOption.Reconnect -> Triple(
|
||||||
|
context.getText(R.string.sync_reconnect),
|
||||||
|
R.drawable.mozac_ic_warning,
|
||||||
|
R.color.default_share_background
|
||||||
|
)
|
||||||
|
SyncShareOption.Offline -> Triple(
|
||||||
|
context.getText(R.string.sync_offline),
|
||||||
|
R.drawable.mozac_ic_warning,
|
||||||
|
R.color.default_share_background
|
||||||
|
)
|
||||||
SyncShareOption.AddNewDevice -> Triple(
|
SyncShareOption.AddNewDevice -> Triple(
|
||||||
context.getText(R.string.sync_connect_device),
|
context.getText(R.string.sync_connect_device),
|
||||||
R.drawable.mozac_ic_new,
|
R.drawable.mozac_ic_new,
|
||||||
|
@ -75,6 +89,7 @@ class AccountDeviceViewHolder(
|
||||||
background.setColorFilter(ContextCompat.getColor(context, colorRes), PorterDuff.Mode.SRC_IN)
|
background.setColorFilter(ContextCompat.getColor(context, colorRes), PorterDuff.Mode.SRC_IN)
|
||||||
drawable.setTint(ContextCompat.getColor(context, R.color.device_foreground))
|
drawable.setTint(ContextCompat.getColor(context, R.color.device_foreground))
|
||||||
}
|
}
|
||||||
|
itemView.isClickable = option != SyncShareOption.Offline
|
||||||
itemView.deviceName.text = name
|
itemView.deviceName.text = name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -490,6 +490,9 @@
|
||||||
app:destination="@+id/turnOnSyncFragment"
|
app:destination="@+id/turnOnSyncFragment"
|
||||||
app:popUpTo="@id/shareFragment"
|
app:popUpTo="@id/shareFragment"
|
||||||
app:popUpToInclusive="true" />
|
app:popUpToInclusive="true" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_shareFragment_to_accountProblemFragment"
|
||||||
|
app:destination="@id/accountProblemFragment" />
|
||||||
</dialog>
|
</dialog>
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/quickSettingsSheetDialogFragment"
|
android:id="@+id/quickSettingsSheetDialogFragment"
|
||||||
|
|
|
@ -175,6 +175,19 @@ class ShareControllerTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `handleReauth should navigate to the Account Problem Fragment and dismiss this one`() {
|
||||||
|
controller.handleReauth()
|
||||||
|
|
||||||
|
verifyOrder {
|
||||||
|
navController.nav(
|
||||||
|
R.id.shareFragment,
|
||||||
|
ShareFragmentDirections.actionShareFragmentToAccountProblemFragment()
|
||||||
|
)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getShareText should respect concatenate shared tabs urls`() {
|
fun `getShareText should respect concatenate shared tabs urls`() {
|
||||||
assertThat(controller.getShareText()).isEqualTo(textToShare)
|
assertThat(controller.getShareText()).isEqualTo(textToShare)
|
||||||
|
|
|
@ -28,6 +28,13 @@ class ShareInteractorTest {
|
||||||
verify { controller.handleSignIn() }
|
verify { controller.handleSignIn() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onReauth() {
|
||||||
|
interactor.onReauth()
|
||||||
|
|
||||||
|
verify { controller.handleReauth() }
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun onAddNewDevice() {
|
fun onAddNewDevice() {
|
||||||
interactor.onAddNewDevice()
|
interactor.onAddNewDevice()
|
||||||
|
|
Loading…
Reference in New Issue