2019-08-28 10:20:32 +02:00
|
|
|
/* 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.share
|
|
|
|
|
2019-09-04 17:46:34 +02:00
|
|
|
import android.content.Context
|
2019-08-28 10:20:32 +02:00
|
|
|
import android.content.Intent
|
|
|
|
import android.content.Intent.ACTION_SEND
|
|
|
|
import android.content.Intent.EXTRA_TEXT
|
|
|
|
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
2019-08-29 17:47:49 +02:00
|
|
|
import androidx.annotation.VisibleForTesting
|
2019-08-28 10:20:32 +02:00
|
|
|
import androidx.navigation.NavController
|
2019-09-12 18:26:24 +02:00
|
|
|
import com.google.android.material.snackbar.Snackbar
|
2019-09-26 21:30:28 +02:00
|
|
|
import kotlinx.coroutines.Deferred
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
import kotlinx.coroutines.GlobalScope
|
|
|
|
import kotlinx.coroutines.launch
|
2019-08-28 10:20:32 +02:00
|
|
|
import mozilla.components.concept.sync.Device
|
2019-09-02 16:20:58 +02:00
|
|
|
import mozilla.components.concept.sync.TabData
|
|
|
|
import mozilla.components.feature.sendtab.SendTabUseCases
|
2019-08-28 10:20:32 +02:00
|
|
|
import org.mozilla.fenix.R
|
2019-09-12 18:26:24 +02:00
|
|
|
import org.mozilla.fenix.components.FenixSnackbar
|
2019-09-26 21:30:28 +02:00
|
|
|
import org.mozilla.fenix.components.FenixSnackbarPresenter
|
2019-09-04 17:46:34 +02:00
|
|
|
import org.mozilla.fenix.components.metrics.Event
|
2019-09-12 18:26:24 +02:00
|
|
|
import org.mozilla.fenix.ext.getRootView
|
2019-09-04 17:46:34 +02:00
|
|
|
import org.mozilla.fenix.ext.metrics
|
2019-08-28 10:20:32 +02:00
|
|
|
import org.mozilla.fenix.ext.nav
|
|
|
|
import org.mozilla.fenix.share.listadapters.AppShareOption
|
|
|
|
|
|
|
|
/**
|
|
|
|
* [ShareFragment] controller.
|
|
|
|
*
|
|
|
|
* Delegated by View Interactors, handles container business logic and operates changes on it.
|
|
|
|
*/
|
|
|
|
interface ShareController {
|
2019-09-05 20:13:03 +02:00
|
|
|
fun handleReauth()
|
2019-08-28 10:20:32 +02:00
|
|
|
fun handleShareClosed()
|
|
|
|
fun handleShareToApp(app: AppShareOption)
|
|
|
|
fun handleAddNewDevice()
|
|
|
|
fun handleShareToDevice(device: Device)
|
|
|
|
fun handleShareToAllDevices(devices: List<Device>)
|
|
|
|
fun handleSignIn()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Default behavior of [ShareController]. Other implementations are possible.
|
|
|
|
*
|
2019-09-26 21:30:28 +02:00
|
|
|
* @param context [Context] used for various Android interactions.
|
2019-09-02 16:20:58 +02:00
|
|
|
* @param sharedTabs the list of [ShareTab]s that can be shared.
|
|
|
|
* @param sendTabUseCases instance of [SendTabUseCases] which allows sending tabs to account devices.
|
2019-09-26 21:30:28 +02:00
|
|
|
* @param snackbarPresenter - instance of [FenixSnackbarPresenter] for displaying styled snackbars
|
2019-08-28 10:20:32 +02:00
|
|
|
* @param navController - [NavController] used for navigation.
|
|
|
|
* @param dismiss - callback signalling sharing can be closed.
|
|
|
|
*/
|
2019-09-26 21:30:28 +02:00
|
|
|
@Suppress("TooManyFunctions")
|
2019-08-28 10:20:32 +02:00
|
|
|
class DefaultShareController(
|
2019-09-04 17:46:34 +02:00
|
|
|
private val context: Context,
|
2019-09-02 16:20:58 +02:00
|
|
|
private val sharedTabs: List<ShareTab>,
|
|
|
|
private val sendTabUseCases: SendTabUseCases,
|
2019-09-26 21:30:28 +02:00
|
|
|
private val snackbarPresenter: FenixSnackbarPresenter,
|
2019-08-28 10:20:32 +02:00
|
|
|
private val navController: NavController,
|
|
|
|
private val dismiss: () -> Unit
|
|
|
|
) : ShareController {
|
2019-09-05 20:13:03 +02:00
|
|
|
override fun handleReauth() {
|
|
|
|
val directions = ShareFragmentDirections.actionShareFragmentToAccountProblemFragment()
|
|
|
|
navController.nav(R.id.shareFragment, directions)
|
|
|
|
dismiss()
|
|
|
|
}
|
|
|
|
|
2019-08-28 10:20:32 +02:00
|
|
|
override fun handleShareClosed() {
|
|
|
|
dismiss()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun handleShareToApp(app: AppShareOption) {
|
|
|
|
val intent = Intent(ACTION_SEND).apply {
|
2019-08-29 17:47:49 +02:00
|
|
|
putExtra(EXTRA_TEXT, getShareText())
|
2019-08-28 10:20:32 +02:00
|
|
|
type = "text/plain"
|
|
|
|
flags = FLAG_ACTIVITY_NEW_TASK
|
|
|
|
setClassName(app.packageName, app.activityName)
|
|
|
|
}
|
2019-09-12 18:26:24 +02:00
|
|
|
|
|
|
|
try {
|
2019-09-26 21:30:28 +02:00
|
|
|
context.startActivity(intent)
|
2019-09-12 18:26:24 +02:00
|
|
|
} catch (e: SecurityException) {
|
|
|
|
context.getRootView()?.let {
|
|
|
|
FenixSnackbar.make(it, Snackbar.LENGTH_LONG)
|
|
|
|
.setText(context.getString(R.string.share_error_snackbar))
|
|
|
|
.show()
|
|
|
|
}
|
|
|
|
}
|
2019-08-28 10:20:32 +02:00
|
|
|
dismiss()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun handleAddNewDevice() {
|
2019-09-16 23:21:06 +02:00
|
|
|
val directions = ShareFragmentDirections.actionShareFragmentToAddNewDeviceFragment()
|
|
|
|
navController.navigate(directions)
|
2019-08-28 10:20:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun handleShareToDevice(device: Device) {
|
2019-09-04 17:46:34 +02:00
|
|
|
context.metrics.track(Event.SendTab)
|
2019-09-26 21:30:28 +02:00
|
|
|
shareToDevicesWithRetry { sendTabUseCases.sendToDeviceAsync(device.id, sharedTabs.toTabData()) }
|
2019-08-28 10:20:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun handleShareToAllDevices(devices: List<Device>) {
|
2019-09-26 21:30:28 +02:00
|
|
|
shareToDevicesWithRetry { sendTabUseCases.sendToAllAsync(sharedTabs.toTabData()) }
|
2019-08-28 10:20:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun handleSignIn() {
|
2019-09-04 17:46:34 +02:00
|
|
|
context.metrics.track(Event.SignInToSendTab)
|
2019-08-28 10:20:32 +02:00
|
|
|
val directions = ShareFragmentDirections.actionShareFragmentToTurnOnSyncFragment()
|
|
|
|
navController.nav(R.id.shareFragment, directions)
|
|
|
|
dismiss()
|
|
|
|
}
|
|
|
|
|
2019-09-26 21:30:28 +02:00
|
|
|
private fun shareToDevicesWithRetry(shareOperation: () -> Deferred<Boolean>) {
|
|
|
|
// Use GlobalScope to allow the continuation of this method even if the share fragment is closed.
|
|
|
|
GlobalScope.launch(Dispatchers.Main) {
|
|
|
|
when (shareOperation.invoke().await()) {
|
|
|
|
true -> showSuccess()
|
|
|
|
false -> showFailureWithRetryOption { shareToDevicesWithRetry(shareOperation) }
|
|
|
|
}
|
|
|
|
dismiss()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
|
|
|
fun showSuccess() {
|
|
|
|
snackbarPresenter.present(
|
|
|
|
getSuccessMessage(),
|
|
|
|
Snackbar.LENGTH_SHORT
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
|
|
|
fun showFailureWithRetryOption(operation: () -> Unit) {
|
|
|
|
snackbarPresenter.present(
|
|
|
|
text = context.getString(R.string.sync_sent_tab_error_snackbar),
|
|
|
|
length = Snackbar.LENGTH_LONG,
|
|
|
|
action = operation,
|
|
|
|
actionName = context.getString(R.string.sync_sent_tab_error_snackbar_action),
|
|
|
|
isError = true
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
|
|
|
fun getSuccessMessage(): String = with(context) {
|
|
|
|
when (sharedTabs.size) {
|
|
|
|
1 -> getString(R.string.sync_sent_tab_snackbar)
|
|
|
|
else -> getString(R.string.sync_sent_tabs_snackbar)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-29 17:47:49 +02:00
|
|
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
2019-09-02 16:20:58 +02:00
|
|
|
fun getShareText() = sharedTabs.joinToString("\n") { tab -> tab.url }
|
2019-08-29 17:47:49 +02:00
|
|
|
|
2019-09-02 16:20:58 +02:00
|
|
|
// Navigation between app fragments uses ShareTab as arguments. SendTabUseCases uses TabData.
|
2019-09-02 16:24:36 +02:00
|
|
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
|
|
|
fun ShareTab.toTabData() = TabData(title, url)
|
2019-09-26 21:30:28 +02:00
|
|
|
|
2019-09-02 16:24:36 +02:00
|
|
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
|
|
|
fun List<ShareTab>.toTabData() = map { it.toTabData() }
|
2019-08-28 10:20:32 +02:00
|
|
|
}
|