Close #14313: Always add copy to clipboard action in share actions
This commit is contained in:
parent
df4d7a9004
commit
d45543ec40
|
@ -5,6 +5,8 @@
|
|||
package org.mozilla.fenix.share
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.ACTION_SEND
|
||||
|
@ -91,6 +93,13 @@ class DefaultShareController(
|
|||
}
|
||||
|
||||
override fun handleShareToApp(app: AppShareOption) {
|
||||
if (app.packageName == ACTION_COPY_LINK_TO_CLIPBOARD) {
|
||||
copyClipboard()
|
||||
dismiss(ShareController.Result.SUCCESS)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
viewLifecycleScope.launch(dispatcher) {
|
||||
recentAppsStorage.updateRecentApp(app.activityName)
|
||||
}
|
||||
|
@ -111,6 +120,7 @@ class DefaultShareController(
|
|||
when (e) {
|
||||
is SecurityException, is ActivityNotFoundException -> {
|
||||
snackbar.setText(context.getString(R.string.share_error_snackbar))
|
||||
snackbar.setLength(FenixSnackbar.LENGTH_LONG)
|
||||
snackbar.show()
|
||||
ShareController.Result.SHARE_ERROR
|
||||
}
|
||||
|
@ -217,4 +227,18 @@ class DefaultShareController(
|
|||
private fun String.toDataUri(): String {
|
||||
return "data:,${Uri.encode(this)}"
|
||||
}
|
||||
|
||||
private fun copyClipboard() {
|
||||
val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clipData = ClipData.newPlainText(getShareSubject(), getShareText())
|
||||
|
||||
clipboardManager.setPrimaryClip(clipData)
|
||||
snackbar.setText(context.getString(R.string.toast_copy_link_to_clipboard))
|
||||
snackbar.setLength(FenixSnackbar.LENGTH_SHORT)
|
||||
snackbar.show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ACTION_COPY_LINK_TO_CLIPBOARD = "org.mozilla.fenix.COPY_LINK_TO_CLIPBOARD"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ class ShareFragment : AppCompatDialogFragment() {
|
|||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
viewModel.loadDevicesAndApps()
|
||||
viewModel.loadDevicesAndApps(requireContext())
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
|
@ -13,6 +13,7 @@ import android.net.Network
|
|||
import android.net.NetworkRequest
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
|
@ -23,8 +24,10 @@ 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.R
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.isOnline
|
||||
import org.mozilla.fenix.share.DefaultShareController.Companion.ACTION_COPY_LINK_TO_CLIPBOARD
|
||||
import org.mozilla.fenix.share.listadapters.AppShareOption
|
||||
import org.mozilla.fenix.share.listadapters.SyncShareOption
|
||||
|
||||
|
@ -79,7 +82,7 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) {
|
|||
* Load a list of devices and apps into [devicesList] and [appsList].
|
||||
* Should be called when the fragment is attached so the data can be fetched early.
|
||||
*/
|
||||
fun loadDevicesAndApps() {
|
||||
fun loadDevicesAndApps(context: Context) {
|
||||
val networkRequest = NetworkRequest.Builder().build()
|
||||
connectivityManager?.registerNetworkCallback(networkRequest, networkCallback)
|
||||
|
||||
|
@ -89,12 +92,18 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) {
|
|||
type = "text/plain"
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
val shareAppsActivities = getIntentActivities(shareIntent, getApplication())
|
||||
var apps = buildAppsList(shareAppsActivities, getApplication())
|
||||
val shareAppsActivities = getIntentActivities(shareIntent, context)
|
||||
|
||||
var apps = buildAppsList(shareAppsActivities, context)
|
||||
recentAppsStorage.updateDatabaseWithNewApps(apps.map { app -> app.activityName })
|
||||
val recentApps = buildRecentAppsList(apps)
|
||||
apps = filterOutRecentApps(apps, recentApps)
|
||||
|
||||
// if copy app is available, prepend to the list of actions
|
||||
getCopyApp(context)?.let {
|
||||
apps = listOf(it) + apps
|
||||
}
|
||||
|
||||
recentAppsListLiveData.postValue(recentApps)
|
||||
appsListLiveData.postValue(apps)
|
||||
}
|
||||
|
@ -105,6 +114,19 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getCopyApp(context: Context): AppShareOption? {
|
||||
val copyIcon = AppCompatResources.getDrawable(context, R.drawable.ic_share_clipboard)
|
||||
|
||||
return copyIcon?.let {
|
||||
AppShareOption(
|
||||
context.getString(R.string.share_copy_link_to_clipboard),
|
||||
copyIcon,
|
||||
ACTION_COPY_LINK_TO_CLIPBOARD,
|
||||
""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun filterOutRecentApps(
|
||||
apps: List<AppShareOption>,
|
||||
recentApps: List<AppShareOption>
|
||||
|
|
25
app/src/main/res/drawable/ic_share_clipboard.xml
Normal file
25
app/src/main/res/drawable/ic_share_clipboard.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/default_share_background" />
|
||||
<padding
|
||||
android:left="10dp"
|
||||
android:top="8dp"
|
||||
android:right="6dp"
|
||||
android:bottom="8dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<vector
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:fillColor="@color/device_foreground"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M10.69,3A3.313,3.313 0,0 1,14 6.31v10.38A3.313,3.313 0,0 1,10.69 20L3.31,20A3.313,3.313 0,0 1,0 16.69L0,6.31A3.313,3.313 0,0 1,3.31 3h7.38zM10.69,5L3.31,5C2.587,5 2,5.587 2,6.31v10.38c0,0.723 0.587,1.31 1.31,1.31h7.38c0.723,0 1.31,-0.587 1.31,-1.31L12,6.31C12,5.587 11.413,5 10.69,5zM10.995,0A6.003,6.003 0,0 1,17 6v0.016l-0.024,8.987a2,2 0,0 1,-2.005 1.994h-0.002l0.03,-10.986L14.999,6c0,-2.21 -1.793,-4 -4.004,-4L3,2a2,2 0,0 1,2 -2h5.995z" />
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -954,6 +954,10 @@
|
|||
<string name="share_link_all_apps_subheader">All actions</string>
|
||||
<!-- Sub-header in the dialog to share a link to an app from the most-recent sorted list -->
|
||||
<string name="share_link_recent_apps_subheader">Recently used</string>
|
||||
<!-- Text for the copy link action in the share screen. -->
|
||||
<string name="share_copy_link_to_clipboard">Copy to clipboard</string>
|
||||
<!-- Toast shown after copying link to clipboard -->
|
||||
<string name="toast_copy_link_to_clipboard">Copied to clipboard</string>
|
||||
<!-- An option from the three dot menu to into sync -->
|
||||
<string name="sync_menu_sign_in">Sign in to sync</string>
|
||||
<!-- An option from the share dialog to sign into sync -->
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.mozilla.fenix.ext.application
|
|||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.isOnline
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
import org.mozilla.fenix.share.DefaultShareController.Companion.ACTION_COPY_LINK_TO_CLIPBOARD
|
||||
import org.mozilla.fenix.share.ShareViewModel.Companion.RECENT_APPS_LIMIT
|
||||
import org.mozilla.fenix.share.listadapters.AppShareOption
|
||||
import org.mozilla.fenix.share.listadapters.SyncShareOption
|
||||
|
@ -100,7 +101,7 @@ class ShareViewModelTest {
|
|||
every { viewModel.buildAppsList(any(), any()) } returns appOptions
|
||||
viewModel.recentAppsStorage = storage
|
||||
|
||||
viewModel.loadDevicesAndApps()
|
||||
viewModel.loadDevicesAndApps(testContext)
|
||||
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
|
||||
|
||||
verify {
|
||||
|
@ -111,7 +112,7 @@ class ShareViewModelTest {
|
|||
}
|
||||
|
||||
assertEquals(1, viewModel.recentAppsList.asFlow().first().size)
|
||||
assertEquals(0, viewModel.appsList.asFlow().first().size)
|
||||
assertEquals(1, viewModel.appsList.asFlow().first().size)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -162,6 +163,45 @@ class ShareViewModelTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN only one app THEN show copy to clipboard before the app`() = runBlockingTest {
|
||||
val appOptions = listOf(
|
||||
AppShareOption("Label", mockk(), "Package", "Activity")
|
||||
)
|
||||
|
||||
val appEntity = mockk<RecentApp>()
|
||||
every { appEntity.activityName } returns "Activity"
|
||||
every { storage.updateDatabaseWithNewApps(appOptions.map { app -> app.packageName }) } just Runs
|
||||
every { storage.getRecentAppsUpTo(RECENT_APPS_LIMIT) } returns emptyList()
|
||||
|
||||
every { viewModel.buildAppsList(any(), any()) } returns appOptions
|
||||
viewModel.recentAppsStorage = storage
|
||||
|
||||
viewModel.loadDevicesAndApps(testContext)
|
||||
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
|
||||
|
||||
assertEquals(0, viewModel.recentAppsList.asFlow().first().size)
|
||||
assertEquals(2, viewModel.appsList.asFlow().first().size)
|
||||
assertEquals(ACTION_COPY_LINK_TO_CLIPBOARD, viewModel.appsList.asFlow().first()[0].packageName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN no app THEN at least have copy to clipboard as app`() = runBlockingTest {
|
||||
val appEntity = mockk<RecentApp>()
|
||||
every { appEntity.activityName } returns "Activity"
|
||||
every { storage.getRecentAppsUpTo(RECENT_APPS_LIMIT) } returns emptyList()
|
||||
|
||||
every { viewModel.buildAppsList(any(), any()) } returns emptyList()
|
||||
viewModel.recentAppsStorage = storage
|
||||
|
||||
viewModel.loadDevicesAndApps(testContext)
|
||||
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
|
||||
|
||||
assertEquals(0, viewModel.recentAppsList.asFlow().first().size)
|
||||
assertEquals(1, viewModel.appsList.asFlow().first().size)
|
||||
assertEquals(ACTION_COPY_LINK_TO_CLIPBOARD, viewModel.appsList.asFlow().first()[0].packageName)
|
||||
}
|
||||
|
||||
private fun createResolveInfo(
|
||||
label: String,
|
||||
icon: Drawable,
|
||||
|
|
Loading…
Reference in New Issue
Block a user