Add custom share sheet and send tab support (#2757)
* Closes #2751: Add custom app share sheet * Closes #2753: Add send tab devices to share sheet * Closes #2752: Add build flag for send tab * Replace Context.share with ShareFragmentmaster
parent
6057c3703a
commit
eb7646f073
|
@ -237,6 +237,11 @@ android.applicationVariants.all { variant ->
|
||||||
buildConfigField 'String', 'LEANPLUM_TOKEN', 'null'
|
buildConfigField 'String', 'LEANPLUM_TOKEN', 'null'
|
||||||
println("X_X")
|
println("X_X")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
// Feature build flags
|
||||||
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
buildConfigField 'Boolean', 'SEND_TAB_ENABLED', "false"
|
||||||
}
|
}
|
||||||
|
|
||||||
androidExtensions {
|
androidExtensions {
|
||||||
|
|
|
@ -75,7 +75,6 @@ import org.mozilla.fenix.components.toolbar.ToolbarViewModel
|
||||||
import org.mozilla.fenix.customtabs.CustomTabsIntegration
|
import org.mozilla.fenix.customtabs.CustomTabsIntegration
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.requireComponents
|
import org.mozilla.fenix.ext.requireComponents
|
||||||
import org.mozilla.fenix.ext.share
|
|
||||||
import org.mozilla.fenix.ext.urlToTrimmedHost
|
import org.mozilla.fenix.ext.urlToTrimmedHost
|
||||||
import org.mozilla.fenix.home.sessioncontrol.Tab
|
import org.mozilla.fenix.home.sessioncontrol.Tab
|
||||||
import org.mozilla.fenix.lib.Do
|
import org.mozilla.fenix.lib.Do
|
||||||
|
@ -439,7 +438,7 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope {
|
||||||
is QuickActionAction.SharePressed -> {
|
is QuickActionAction.SharePressed -> {
|
||||||
requireComponents.analytics.metrics.track(Event.QuickActionSheetShareTapped)
|
requireComponents.analytics.metrics.track(Event.QuickActionSheetShareTapped)
|
||||||
getSessionById()?.let { session ->
|
getSessionById()?.let { session ->
|
||||||
session.url.apply { requireContext().share(this) }
|
shareUrl(session.url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is QuickActionAction.DownloadsPressed -> {
|
is QuickActionAction.DownloadsPressed -> {
|
||||||
|
@ -616,7 +615,7 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope {
|
||||||
is ToolbarMenu.Item.RequestDesktop -> sessionUseCases.requestDesktopSite.invoke(action.item.isChecked)
|
is ToolbarMenu.Item.RequestDesktop -> sessionUseCases.requestDesktopSite.invoke(action.item.isChecked)
|
||||||
ToolbarMenu.Item.Share -> getSessionById()?.let { session ->
|
ToolbarMenu.Item.Share -> getSessionById()?.let { session ->
|
||||||
session.url.apply {
|
session.url.apply {
|
||||||
requireContext().share(this)
|
shareUrl(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToolbarMenu.Item.NewPrivateTab -> {
|
ToolbarMenu.Item.NewPrivateTab -> {
|
||||||
|
@ -766,6 +765,11 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun shareUrl(url: String) {
|
||||||
|
val directions = BrowserFragmentDirections.actionBrowserFragmentToShareFragment(url)
|
||||||
|
Navigation.findNavController(view!!).navigate(directions)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val REQUEST_CODE_DOWNLOAD_PERMISSIONS = 1
|
private const val REQUEST_CODE_DOWNLOAD_PERMISSIONS = 1
|
||||||
private const val REQUEST_CODE_PROMPT_PERMISSIONS = 2
|
private const val REQUEST_CODE_PROMPT_PERMISSIONS = 2
|
||||||
|
|
|
@ -51,6 +51,7 @@ fun Context.getPreferenceKey(@StringRes resourceId: Int): String =
|
||||||
* @param subject of the intent [EXTRA_TEXT]
|
* @param subject of the intent [EXTRA_TEXT]
|
||||||
* @return true it is able to share false otherwise.
|
* @return true it is able to share false otherwise.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("We are replacing the system share sheet with a custom version. See: [ShareFragment]")
|
||||||
fun Context.share(text: String, subject: String = ""): Boolean {
|
fun Context.share(text: String, subject: String = ""): Boolean {
|
||||||
return try {
|
return try {
|
||||||
val intent = Intent(ACTION_SEND).apply {
|
val intent = Intent(ACTION_SEND).apply {
|
||||||
|
|
|
@ -45,7 +45,6 @@ import org.mozilla.fenix.collections.SaveCollectionStep
|
||||||
import org.mozilla.fenix.components.metrics.Event
|
import org.mozilla.fenix.components.metrics.Event
|
||||||
import org.mozilla.fenix.ext.allowUndo
|
import org.mozilla.fenix.ext.allowUndo
|
||||||
import org.mozilla.fenix.ext.requireComponents
|
import org.mozilla.fenix.ext.requireComponents
|
||||||
import org.mozilla.fenix.ext.share
|
|
||||||
import org.mozilla.fenix.ext.urlToTrimmedHost
|
import org.mozilla.fenix.ext.urlToTrimmedHost
|
||||||
import org.mozilla.fenix.home.sessioncontrol.CollectionAction
|
import org.mozilla.fenix.home.sessioncontrol.CollectionAction
|
||||||
import org.mozilla.fenix.home.sessioncontrol.Mode
|
import org.mozilla.fenix.home.sessioncontrol.Mode
|
||||||
|
@ -302,7 +301,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver {
|
||||||
invokePendingDeleteJobs()
|
invokePendingDeleteJobs()
|
||||||
requireComponents.core.sessionManager.findSessionById(action.sessionId)
|
requireComponents.core.sessionManager.findSessionById(action.sessionId)
|
||||||
?.let { session ->
|
?.let { session ->
|
||||||
requireContext().share(session.url)
|
share(session.url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is TabAction.CloseAll -> {
|
is TabAction.CloseAll -> {
|
||||||
|
@ -326,7 +325,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver {
|
||||||
val shareText = requireComponents.core.sessionManager.sessions.joinToString("\n") {
|
val shareText = requireComponents.core.sessionManager.sessions.joinToString("\n") {
|
||||||
it.url
|
it.url
|
||||||
}
|
}
|
||||||
requireContext().share(shareText)
|
share(shareText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -397,7 +396,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver {
|
||||||
val shareText = action.collection.tabs.joinToString("\n") {
|
val shareText = action.collection.tabs.joinToString("\n") {
|
||||||
it.url
|
it.url
|
||||||
}
|
}
|
||||||
requireContext().share(shareText)
|
share(shareText)
|
||||||
}
|
}
|
||||||
is CollectionAction.RemoveTab -> {
|
is CollectionAction.RemoveTab -> {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
|
@ -648,6 +647,11 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun share(text: String) {
|
||||||
|
val directions = HomeFragmentDirections.actionHomeFragmentToShareFragment(text)
|
||||||
|
Navigation.findNavController(view!!).navigate(directions)
|
||||||
|
}
|
||||||
|
|
||||||
private fun currentMode(): Mode = if (!onboarding.userHasBeenOnboarded()) {
|
private fun currentMode(): Mode = if (!onboarding.userHasBeenOnboarded()) {
|
||||||
val account = requireComponents.backgroundServices.accountManager.authenticatedAccount()
|
val account = requireComponents.backgroundServices.accountManager.authenticatedAccount()
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
|
|
|
@ -46,7 +46,6 @@ import org.mozilla.fenix.components.metrics.Event
|
||||||
import org.mozilla.fenix.ext.allowUndo
|
import org.mozilla.fenix.ext.allowUndo
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.requireComponents
|
import org.mozilla.fenix.ext.requireComponents
|
||||||
import org.mozilla.fenix.ext.share
|
|
||||||
import org.mozilla.fenix.ext.urlToTrimmedHost
|
import org.mozilla.fenix.ext.urlToTrimmedHost
|
||||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||||
import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
||||||
|
@ -207,7 +206,10 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve
|
||||||
}
|
}
|
||||||
is BookmarkAction.Share -> {
|
is BookmarkAction.Share -> {
|
||||||
it.item.url?.apply {
|
it.item.url?.apply {
|
||||||
requireContext().share(this)
|
navigation
|
||||||
|
.navigate(
|
||||||
|
BookmarkFragmentDirections.actionBookmarkFragmentToShareFragment(this)
|
||||||
|
)
|
||||||
requireComponents.analytics.metrics.track(Event.ShareBookmark)
|
requireComponents.analytics.metrics.track(Event.ShareBookmark)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,6 @@ import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.components.Components
|
import org.mozilla.fenix.components.Components
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.requireComponents
|
import org.mozilla.fenix.ext.requireComponents
|
||||||
import org.mozilla.fenix.ext.share
|
|
||||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||||
import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
||||||
import org.mozilla.fenix.mvi.getManagedEmitter
|
import org.mozilla.fenix.mvi.getManagedEmitter
|
||||||
|
@ -182,12 +181,12 @@ class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
|
||||||
R.id.share_history_multi_select -> {
|
R.id.share_history_multi_select -> {
|
||||||
val selectedHistory = (historyComponent.uiView as HistoryUIView).getSelected()
|
val selectedHistory = (historyComponent.uiView as HistoryUIView).getSelected()
|
||||||
when {
|
when {
|
||||||
selectedHistory.size == 1 -> context?.share(selectedHistory.first().url)
|
selectedHistory.size == 1 -> share(selectedHistory.first().url)
|
||||||
selectedHistory.size > 1 -> {
|
selectedHistory.size > 1 -> {
|
||||||
val shareText = selectedHistory.joinToString("\n") {
|
val shareText = selectedHistory.joinToString("\n") {
|
||||||
it.url
|
it.url
|
||||||
}
|
}
|
||||||
requireContext().share(shareText)
|
share(shareText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
@ -283,4 +282,9 @@ class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
|
||||||
components.core.historyStorage.deleteVisit(it.url, it.visitedAt)
|
components.core.historyStorage.deleteVisit(it.url, it.visitedAt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun share(text: String) {
|
||||||
|
val directions = HistoryFragmentDirections.actionHistoryFragmentToShareFragment(text)
|
||||||
|
Navigation.findNavController(view!!).navigate(directions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
/* 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
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.reactivex.Observer
|
||||||
|
import kotlinx.android.synthetic.main.account_share_list_item.view.*
|
||||||
|
import mozilla.components.concept.sync.Device
|
||||||
|
import mozilla.components.concept.sync.DeviceCapability
|
||||||
|
import mozilla.components.concept.sync.DeviceType
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
|
||||||
|
class AccountDevicesShareRecyclerView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : RecyclerView(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccountDevicesShareAdapter(
|
||||||
|
private val context: Context,
|
||||||
|
val actionEmitter: Observer<ShareAction>
|
||||||
|
) : RecyclerView.Adapter<AccountDeviceViewHolder>() {
|
||||||
|
|
||||||
|
private val devices = buildDeviceList()
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountDeviceViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(AccountDeviceViewHolder.LAYOUT_ID, parent, false)
|
||||||
|
return AccountDeviceViewHolder(view, actionEmitter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = devices.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: AccountDeviceViewHolder, position: Int) {
|
||||||
|
holder.bind(devices[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildDeviceList(): List<SyncShareOption> {
|
||||||
|
val list = mutableListOf<SyncShareOption>()
|
||||||
|
val accountManager = context.components.backgroundServices.accountManager
|
||||||
|
|
||||||
|
if (accountManager.authenticatedAccount() == null) {
|
||||||
|
list.add(SyncShareOption.SignIn)
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
list.add(SyncShareOption.AddNewDevice)
|
||||||
|
|
||||||
|
accountManager.authenticatedAccount()?.deviceConstellation()?.state()?.otherDevices?.let { devices ->
|
||||||
|
val shareableDevices = devices
|
||||||
|
.filter {
|
||||||
|
it.capabilities.contains(DeviceCapability.SEND_TAB)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccountDeviceViewHolder(
|
||||||
|
itemView: View,
|
||||||
|
actionEmitter: Observer<ShareAction>
|
||||||
|
) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
||||||
|
private val context: Context = itemView.context
|
||||||
|
private var action: ShareAction? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
action?.let { actionEmitter.onNext(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(option: SyncShareOption) {
|
||||||
|
val (name, drawableRes, colorRes) = when (option) {
|
||||||
|
SyncShareOption.SignIn -> {
|
||||||
|
action = ShareAction.SignInClicked
|
||||||
|
Triple(
|
||||||
|
context.getText(R.string.sync_sign_in),
|
||||||
|
R.drawable.mozac_ic_sync,
|
||||||
|
R.color.default_share_background
|
||||||
|
)
|
||||||
|
}
|
||||||
|
SyncShareOption.AddNewDevice -> {
|
||||||
|
action = ShareAction.AddNewDeviceClicked
|
||||||
|
Triple(
|
||||||
|
context.getText(R.string.sync_connect_device),
|
||||||
|
R.drawable.mozac_ic_new,
|
||||||
|
R.color.default_share_background
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is SyncShareOption.SendAll -> {
|
||||||
|
action = ShareAction.SendAllClicked(option.devices)
|
||||||
|
Triple(
|
||||||
|
context.getText(R.string.sync_send_to_all),
|
||||||
|
R.drawable.mozac_ic_select_all,
|
||||||
|
R.color.default_share_background
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is SyncShareOption.Mobile -> {
|
||||||
|
action = ShareAction.ShareDeviceClicked(option.device)
|
||||||
|
Triple(
|
||||||
|
option.name,
|
||||||
|
R.drawable.mozac_ic_device_mobile,
|
||||||
|
R.color.device_type_mobile_background
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is SyncShareOption.Desktop -> {
|
||||||
|
action = ShareAction.ShareDeviceClicked(option.device)
|
||||||
|
Triple(
|
||||||
|
option.name,
|
||||||
|
R.drawable.mozac_ic_device_desktop,
|
||||||
|
R.color.device_type_desktop_background
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
itemView.device_icon.apply {
|
||||||
|
setImageResource(drawableRes)
|
||||||
|
background.setColorFilter(ContextCompat.getColor(context, colorRes), PorterDuff.Mode.SRC_IN)
|
||||||
|
drawable.setTint(ContextCompat.getColor(context, R.color.device_foreground))
|
||||||
|
}
|
||||||
|
itemView.device_name.text = name
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.account_share_list_item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SyncShareOption {
|
||||||
|
object SignIn : SyncShareOption()
|
||||||
|
object AddNewDevice : SyncShareOption()
|
||||||
|
data class SendAll(val devices: List<Device>) : SyncShareOption()
|
||||||
|
data class Mobile(val name: String, val device: Device) : SyncShareOption()
|
||||||
|
data class Desktop(val name: String, val device: Device) : SyncShareOption()
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
/* 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
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.Intent.ACTION_SEND
|
||||||
|
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.reactivex.Observer
|
||||||
|
import kotlinx.android.synthetic.main.app_share_list_item.view.*
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
class AppShareRecyclerView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : RecyclerView(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
layoutManager = GridLayoutManager(context, 2, GridLayoutManager.HORIZONTAL, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppShareAdapter(
|
||||||
|
private val context: Context,
|
||||||
|
val actionEmitter: Observer<ShareAction>,
|
||||||
|
private val intentType: String = "text/plain"
|
||||||
|
) : RecyclerView.Adapter<AppShareItemViewHolder>(), CoroutineScope {
|
||||||
|
|
||||||
|
private var job: Job = Job()
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = Dispatchers.IO + job
|
||||||
|
private var size: Int = 0
|
||||||
|
private val shareItems: MutableList<ShareItem> = mutableListOf()
|
||||||
|
|
||||||
|
init {
|
||||||
|
val testIntent = Intent(ACTION_SEND).apply {
|
||||||
|
type = intentType
|
||||||
|
flags = FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
val activities = context.packageManager.queryIntentActivities(testIntent, 0)
|
||||||
|
|
||||||
|
val items = activities.map { resolveInfo ->
|
||||||
|
ShareItem(
|
||||||
|
resolveInfo.loadLabel(context.packageManager).toString(),
|
||||||
|
resolveInfo.loadIcon(context.packageManager),
|
||||||
|
resolveInfo.activityInfo.packageName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
size = activities.size
|
||||||
|
shareItems.addAll(items)
|
||||||
|
|
||||||
|
// Notify adapter on the UI thread when the dataset is populated.
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppShareItemViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(AppShareItemViewHolder.LAYOUT_ID, parent, false)
|
||||||
|
return AppShareItemViewHolder(view, actionEmitter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: AppShareItemViewHolder, position: Int) {
|
||||||
|
holder.bind(shareItems[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||||
|
super.onAttachedToRecyclerView(recyclerView)
|
||||||
|
job = Job()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
||||||
|
super.onDetachedFromRecyclerView(recyclerView)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppShareItemViewHolder(
|
||||||
|
itemView: View,
|
||||||
|
actionEmitter: Observer<ShareAction>
|
||||||
|
) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
||||||
|
private var shareItem: ShareItem? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
Log.d("Jonathan", "${shareItem?.name} clicked.")
|
||||||
|
shareItem?.let {
|
||||||
|
actionEmitter.onNext(ShareAction.ShareAppClicked(it.packageName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun bind(item: ShareItem) {
|
||||||
|
shareItem = item
|
||||||
|
itemView.app_name.text = item.name
|
||||||
|
itemView.app_icon.setImageDrawable(item.icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.app_share_list_item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ShareItem(val name: String, val icon: Drawable, val packageName: String)
|
|
@ -0,0 +1,56 @@
|
||||||
|
package org.mozilla.fenix.share
|
||||||
|
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import mozilla.components.concept.sync.Device
|
||||||
|
import org.mozilla.fenix.mvi.Action
|
||||||
|
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||||
|
import org.mozilla.fenix.mvi.Change
|
||||||
|
import org.mozilla.fenix.mvi.Reducer
|
||||||
|
import org.mozilla.fenix.mvi.UIComponent
|
||||||
|
import org.mozilla.fenix.mvi.UIComponentViewModelBase
|
||||||
|
import org.mozilla.fenix.mvi.UIComponentViewModelProvider
|
||||||
|
import org.mozilla.fenix.mvi.ViewState
|
||||||
|
|
||||||
|
object ShareState : ViewState
|
||||||
|
|
||||||
|
sealed class ShareChange : Change
|
||||||
|
|
||||||
|
sealed class ShareAction : Action {
|
||||||
|
object Close : ShareAction()
|
||||||
|
object SignInClicked : ShareAction()
|
||||||
|
object AddNewDeviceClicked : ShareAction()
|
||||||
|
data class ShareDeviceClicked(val device: Device) : ShareAction()
|
||||||
|
data class SendAllClicked(val devices: List<Device>) : ShareAction()
|
||||||
|
data class ShareAppClicked(val packageName: String) : ShareAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShareComponent(
|
||||||
|
private val container: ViewGroup,
|
||||||
|
bus: ActionBusFactory,
|
||||||
|
viewModelProvider: UIComponentViewModelProvider<ShareState, ShareChange>
|
||||||
|
) : UIComponent<ShareState, ShareAction, ShareChange>(
|
||||||
|
bus.getManagedEmitter(ShareAction::class.java),
|
||||||
|
bus.getSafeManagedObservable(ShareChange::class.java),
|
||||||
|
viewModelProvider
|
||||||
|
) {
|
||||||
|
override fun initView() = ShareUIView(container, actionEmitter, changesObservable)
|
||||||
|
|
||||||
|
init {
|
||||||
|
bind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShareUIViewModel(
|
||||||
|
initialState: ShareState
|
||||||
|
) : UIComponentViewModelBase<ShareState, ShareChange>(
|
||||||
|
initialState,
|
||||||
|
reducer
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val reducer: Reducer<ShareState, ShareChange> = { _, _ -> ShareState }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package org.mozilla.fenix.share
|
||||||
|
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.Intent.ACTION_SEND
|
||||||
|
import android.content.Intent.EXTRA_TEXT
|
||||||
|
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import kotlinx.android.synthetic.main.fragment_share.view.*
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import org.mozilla.fenix.FenixViewModelProvider
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.ext.requireComponents
|
||||||
|
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||||
|
import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
class ShareFragment : DialogFragment(), CoroutineScope {
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = Dispatchers.Main + job
|
||||||
|
private lateinit var job: Job
|
||||||
|
private lateinit var component: ShareComponent
|
||||||
|
private lateinit var url: String
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setStyle(STYLE_NO_TITLE, R.style.CreateCollectionDialogStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = inflater.inflate(R.layout.fragment_share, container, false)
|
||||||
|
val args = ShareFragmentArgs.fromBundle(arguments!!)
|
||||||
|
|
||||||
|
job = Job()
|
||||||
|
url = args.url
|
||||||
|
component = ShareComponent(
|
||||||
|
view.share_wrapper,
|
||||||
|
ActionBusFactory.get(this),
|
||||||
|
FenixViewModelProvider.create(
|
||||||
|
this,
|
||||||
|
ShareUIViewModel::class.java
|
||||||
|
) {
|
||||||
|
ShareUIViewModel(ShareState)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
subscribeToActions()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun subscribeToActions() {
|
||||||
|
getAutoDisposeObservable<ShareAction>().subscribe {
|
||||||
|
when (it) {
|
||||||
|
ShareAction.Close -> {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
ShareAction.AddNewDeviceClicked -> {
|
||||||
|
requireComponents.useCases.tabsUseCases.addTab.invoke(ADD_NEW_DEVICES_URL, true)
|
||||||
|
}
|
||||||
|
is ShareAction.ShareAppClicked -> {
|
||||||
|
val intent = Intent(ACTION_SEND).apply {
|
||||||
|
putExtra(EXTRA_TEXT, url)
|
||||||
|
type = "text/plain"
|
||||||
|
flags = FLAG_ACTIVITY_NEW_TASK
|
||||||
|
`package` = it.packageName
|
||||||
|
}
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
// TODO support other actions in a follow-up issue
|
||||||
|
}
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ADD_NEW_DEVICES_URL = "https://accounts.firefox.com/connect_another_device"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/* 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
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.Observer
|
||||||
|
import io.reactivex.functions.Consumer
|
||||||
|
import kotlinx.android.synthetic.main.component_share.*
|
||||||
|
import org.mozilla.fenix.BuildConfig
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.mvi.UIView
|
||||||
|
|
||||||
|
class ShareUIView(
|
||||||
|
container: ViewGroup,
|
||||||
|
actionEmitter: Observer<ShareAction>,
|
||||||
|
changesObservable: Observable<ShareChange>
|
||||||
|
) : UIView<ShareState, ShareAction, ShareChange>(
|
||||||
|
container,
|
||||||
|
actionEmitter,
|
||||||
|
changesObservable
|
||||||
|
) {
|
||||||
|
override val view: View = LayoutInflater.from(container.context)
|
||||||
|
.inflate(R.layout.component_share, container, true)
|
||||||
|
|
||||||
|
init {
|
||||||
|
val adapter = AppShareAdapter(view.context, actionEmitter).also {
|
||||||
|
it.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||||
|
override fun onChanged() {
|
||||||
|
progress_bar.visibility = View.GONE
|
||||||
|
intent_handler_recyclerview.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
intent_handler_recyclerview.adapter = adapter
|
||||||
|
|
||||||
|
if (BuildConfig.SEND_TAB_ENABLED) {
|
||||||
|
account_devices_recyclerview.adapter = AccountDevicesShareAdapter(view.context, actionEmitter)
|
||||||
|
} else {
|
||||||
|
send_tab_group.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
close_button.setOnClickListener { actionEmitter.onNext(ShareAction.Close) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateView() = Consumer<ShareState> {
|
||||||
|
ShareState
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?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"
|
||||||
|
android:shape="oval">
|
||||||
|
<size
|
||||||
|
android:height="40dp"
|
||||||
|
android:width="40dp" />
|
||||||
|
</shape>
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?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"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:layout_width="76dp"
|
||||||
|
android:layout_height="80dp">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:id="@+id/device_icon"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/device_background"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/device_name"
|
||||||
|
tools:srcCompat="@tools:sample/avatars"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/device_name"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:textAlignment="gravity"
|
||||||
|
android:gravity="center|top"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:lines="2"
|
||||||
|
android:layout_marginTop="3dp"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/device_icon"
|
||||||
|
tools:text="Firefox on Macbook Pro"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?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"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:layout_width="76dp"
|
||||||
|
android:layout_height="80dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:id="@+id/app_icon"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/app_name"
|
||||||
|
tools:srcCompat="@tools:sample/avatars"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/app_name"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:textAlignment="gravity"
|
||||||
|
android:gravity="center|top"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:lines="2"
|
||||||
|
android:layout_marginTop="3dp"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/app_icon"
|
||||||
|
tools:text="Copy to clipboard"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,127 @@
|
||||||
|
<?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"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/collection_constraint_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/close_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:drawableStart="@drawable/mozac_ic_close"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:text="@string/share_header"
|
||||||
|
android:textAppearance="@style/HeaderTextStyle"
|
||||||
|
android:textColor="@color/neutral_text"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardBackgroundColor="?above"
|
||||||
|
app:cardCornerRadius="@dimen/tab_corner_radius"
|
||||||
|
app:cardElevation="5dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Group
|
||||||
|
android:id="@+id/send_tab_group"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:constraint_referenced_ids="account_header,account_devices_recyclerview,divider_line"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:text="@string/share_device_subheader"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textColor="?secondaryText"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/account_header"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="12dp"/>
|
||||||
|
|
||||||
|
<org.mozilla.fenix.share.AccountDevicesShareRecyclerView
|
||||||
|
android:id="@+id/account_devices_recyclerview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/account_header"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/divider_line"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="?neutralFaded"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/account_devices_recyclerview"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:text="@string/share_link_subheader"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textColor="?secondaryText"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:id="@+id/link_header"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/divider_line"/>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
android:layout_width="76dp"
|
||||||
|
android:layout_height="37dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/link_header"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
|
<org.mozilla.fenix.share.AppShareRecyclerView
|
||||||
|
android:id="@+id/intent_handler_recyclerview"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/link_header"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?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/. -->
|
||||||
|
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/share_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/scrim_background"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
tools:context="org.mozilla.fenix.share.ShareFragment">
|
||||||
|
|
||||||
|
|
||||||
|
</FrameLayout>
|
|
@ -38,6 +38,9 @@
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_homeFragment_to_createCollectionFragment"
|
android:id="@+id/action_homeFragment_to_createCollectionFragment"
|
||||||
app:destination="@id/createCollectionFragment" />
|
app:destination="@id/createCollectionFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_homeFragment_to_shareFragment"
|
||||||
|
app:destination="@id/shareFragment" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
|
@ -132,6 +135,9 @@
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_browserFragment_to_createCollectionFragment"
|
android:id="@+id/action_browserFragment_to_createCollectionFragment"
|
||||||
app:destination="@id/createCollectionFragment" />
|
app:destination="@id/createCollectionFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_browserFragment_to_shareFragment"
|
||||||
|
app:destination="@id/shareFragment" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_browserFragment_to_quickSettingsSheetDialogFragment"
|
android:id="@+id/action_browserFragment_to_quickSettingsSheetDialogFragment"
|
||||||
app:destination="@id/quickSettingsSheetDialogFragment" />
|
app:destination="@id/quickSettingsSheetDialogFragment" />
|
||||||
|
@ -161,6 +167,9 @@
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_historyFragment_to_homeFragment"
|
android:id="@+id/action_historyFragment_to_homeFragment"
|
||||||
app:destination="@id/homeFragment" />
|
app:destination="@id/homeFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_historyFragment_to_shareFragment"
|
||||||
|
app:destination="@id/shareFragment" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
|
@ -185,6 +194,9 @@
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_bookmarkFragment_to_homeFragment"
|
android:id="@+id/action_bookmarkFragment_to_homeFragment"
|
||||||
app:destination="@id/homeFragment" />
|
app:destination="@id/homeFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_bookmarkFragment_to_shareFragment"
|
||||||
|
app:destination="@id/shareFragment" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
|
@ -374,6 +386,15 @@
|
||||||
android:name="org.mozilla.fenix.collections.CreateCollectionFragment"
|
android:name="org.mozilla.fenix.collections.CreateCollectionFragment"
|
||||||
android:label="fragment_create_collection"
|
android:label="fragment_create_collection"
|
||||||
tools:layout="@layout/fragment_create_collection" />
|
tools:layout="@layout/fragment_create_collection" />
|
||||||
|
<dialog
|
||||||
|
android:id="@+id/shareFragment"
|
||||||
|
android:name="org.mozilla.fenix.share.ShareFragment"
|
||||||
|
android:label="fragment_share"
|
||||||
|
tools:layout="@layout/fragment_share" >
|
||||||
|
<argument
|
||||||
|
android:name="url"
|
||||||
|
app:argType="string" />
|
||||||
|
</dialog>
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/quickSettingsSheetDialogFragment"
|
android:id="@+id/quickSettingsSheetDialogFragment"
|
||||||
android:name="org.mozilla.fenix.settings.quicksettings.QuickSettingsSheetDialogFragment"
|
android:name="org.mozilla.fenix.settings.quicksettings.QuickSettingsSheetDialogFragment"
|
||||||
|
|
|
@ -137,7 +137,14 @@
|
||||||
<!-- Reader View colors -->
|
<!-- Reader View colors -->
|
||||||
<color name="mozac_feature_readerview_text_color">@color/primary_text_light_theme</color>
|
<color name="mozac_feature_readerview_text_color">@color/primary_text_light_theme</color>
|
||||||
|
|
||||||
|
<!-- Onboarding colors -->
|
||||||
<color name="onboarding_card_background_dark">#20123A</color>
|
<color name="onboarding_card_background_dark">#20123A</color>
|
||||||
<color name="onboarding_card_primary_text_dark">#3FE1B0</color>
|
<color name="onboarding_card_primary_text_dark">#3FE1B0</color>
|
||||||
<color name="onboarding_card_button_background_dark">#312A65</color>
|
<color name="onboarding_card_button_background_dark">#312A65</color>
|
||||||
|
|
||||||
|
<!-- Share UI -->
|
||||||
|
<color name="default_share_background">#E3E2E3</color>
|
||||||
|
<color name="device_type_desktop_background">#F091C3</color>
|
||||||
|
<color name="device_type_mobile_background">#D4C1FA</color>
|
||||||
|
<color name="device_foreground">#20123A</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -477,6 +477,24 @@
|
||||||
<!-- Default name for a new collection in "name new collection" step of the collection creator. %d is a placeholder for the number of collections-->
|
<!-- Default name for a new collection in "name new collection" step of the collection creator. %d is a placeholder for the number of collections-->
|
||||||
<string name="create_collection_default_name">Collection %d</string>
|
<string name="create_collection_default_name">Collection %d</string>
|
||||||
|
|
||||||
|
<!-- Share -->
|
||||||
|
<!-- Share screen header -->
|
||||||
|
<string name="share_header">Send and Share</string>
|
||||||
|
<!-- Sub-header in the dialog to share a link to another app -->
|
||||||
|
<string name="share_link_subheader">Share a link</string>
|
||||||
|
<!-- Sub-header in the dialog to share a link to another sync device -->
|
||||||
|
<string name="share_device_subheader">Send to device</string>
|
||||||
|
<!-- An option from the share dialog to sign into sync -->
|
||||||
|
<string name="sync_sign_in">Sign in to Sync</string>
|
||||||
|
<!-- An option from the share dialog to send link to all other sync devices -->
|
||||||
|
<string name="sync_send_to_all">Send to all devices</string>
|
||||||
|
<!-- An option from the share dialog to reconnect to sync -->
|
||||||
|
<string name="sync_reconnect">Reconnect to Sync</string>
|
||||||
|
<!-- An option from the share dialog to reconnect to sync -->
|
||||||
|
<string name="sync_offline">Offline</string>
|
||||||
|
<!-- An option to connect additional devices -->
|
||||||
|
<string name="sync_connect_device">Connect another device</string>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<!-- Text shown in snackbar when user deletes a collection -->
|
<!-- Text shown in snackbar when user deletes a collection -->
|
||||||
<string name="snackbar_collection_deleted">Collection deleted</string>
|
<string name="snackbar_collection_deleted">Collection deleted</string>
|
||||||
|
|
Loading…
Reference in New Issue