From 095870145fe51501977613524aa994f35d22c541 Mon Sep 17 00:00:00 2001 From: Mugurell Date: Fri, 23 Aug 2019 13:54:03 +0300 Subject: [PATCH] For #4007 - Refactor AppShareView in standalone Share Views In an effort to respect the initial MVI architecture I've broken the complex `AppShareView` in 3 separate Views - `ShareCloseView` - `ShareToAccountDevicesView` - `ShareToAppsView` They are standalone Views (extending LayoutContainer) which know nothing about each other or their parent and so offer their container the possibility to order or display them in any form later. According to the lib-state contract they are only responsible to - inflate themselves in their injected containerView - render a certain state (to be added in later commits) - delegate all user interaction to an associated Interactor --- .../fenix/share/AccountDevicesShareView.kt | 150 ------------------ .../org/mozilla/fenix/share/AppShareView.kt | 108 ------------- .../org/mozilla/fenix/share/ShareCloseView.kt | 30 ++++ .../org/mozilla/fenix/share/ShareComponent.kt | 56 ------- .../org/mozilla/fenix/share/ShareFragment.kt | 107 ++----------- .../mozilla/fenix/share/ShareInteractor.kt | 37 +++++ .../fenix/share/ShareToAccountDevicesView.kt | 31 ++++ .../mozilla/fenix/share/ShareToAppsView.kt | 28 ++++ .../org/mozilla/fenix/share/ShareUIView.kt | 56 ------- .../listadapters/AccountDevicesAdapter.kt | 45 ++++++ .../share/listadapters/AppShareAdapter.kt | 44 +++++ .../viewholders/AccountDeviceViewHolder.kt | 82 ++++++++++ .../fenix/share/viewholders/AppViewHolder.kt | 38 +++++ app/src/main/res/layout/component_share.xml | 128 --------------- app/src/main/res/layout/fragment_share.xml | 49 +++++- app/src/main/res/layout/share_close.xml | 24 +++ .../res/layout/share_to_account_devices.xml | 41 +++++ app/src/main/res/layout/share_to_apps.xml | 54 +++++++ 18 files changed, 510 insertions(+), 598 deletions(-) delete mode 100644 app/src/main/java/org/mozilla/fenix/share/AccountDevicesShareView.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/share/AppShareView.kt create mode 100644 app/src/main/java/org/mozilla/fenix/share/ShareCloseView.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/share/ShareComponent.kt create mode 100644 app/src/main/java/org/mozilla/fenix/share/ShareInteractor.kt create mode 100644 app/src/main/java/org/mozilla/fenix/share/ShareToAccountDevicesView.kt create mode 100644 app/src/main/java/org/mozilla/fenix/share/ShareToAppsView.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/share/ShareUIView.kt create mode 100644 app/src/main/java/org/mozilla/fenix/share/listadapters/AccountDevicesAdapter.kt create mode 100644 app/src/main/java/org/mozilla/fenix/share/listadapters/AppShareAdapter.kt create mode 100644 app/src/main/java/org/mozilla/fenix/share/viewholders/AccountDeviceViewHolder.kt create mode 100644 app/src/main/java/org/mozilla/fenix/share/viewholders/AppViewHolder.kt delete mode 100644 app/src/main/res/layout/component_share.xml create mode 100644 app/src/main/res/layout/share_close.xml create mode 100644 app/src/main/res/layout/share_to_account_devices.xml create mode 100644 app/src/main/res/layout/share_to_apps.xml diff --git a/app/src/main/java/org/mozilla/fenix/share/AccountDevicesShareView.kt b/app/src/main/java/org/mozilla/fenix/share/AccountDevicesShareView.kt deleted file mode 100644 index d7d37295e..000000000 --- a/app/src/main/java/org/mozilla/fenix/share/AccountDevicesShareView.kt +++ /dev/null @@ -1,150 +0,0 @@ -/* 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.Mode.SRC_IN -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.content.ContextCompat -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 AccountDevicesShareAdapter( - private val context: Context, - val actionEmitter: Observer -) : RecyclerView.Adapter() { - - 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 { - val list = mutableListOf() - val accountManager = context.components.backgroundServices.accountManager - - 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 - } -} - -class AccountDeviceViewHolder( - itemView: View, - actionEmitter: Observer -) : 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.deviceIcon.apply { - setImageResource(drawableRes) - background.setColorFilter(ContextCompat.getColor(context, colorRes), SRC_IN) - drawable.setTint(ContextCompat.getColor(context, R.color.device_foreground)) - } - itemView.deviceName.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) : SyncShareOption() - data class Mobile(val name: String, val device: Device) : SyncShareOption() - data class Desktop(val name: String, val device: Device) : SyncShareOption() -} diff --git a/app/src/main/java/org/mozilla/fenix/share/AppShareView.kt b/app/src/main/java/org/mozilla/fenix/share/AppShareView.kt deleted file mode 100644 index d34ada1e9..000000000 --- a/app/src/main/java/org/mozilla/fenix/share/AppShareView.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* 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.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -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.cancel -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.mozilla.fenix.R - -class AppShareAdapter( - private val context: Context, - val actionEmitter: Observer, - private val intentType: String = "text/plain" -) : RecyclerView.Adapter() { - - private var scope = CoroutineScope(Dispatchers.IO) - - private var size: Int = 0 - private val shareItems: MutableList = mutableListOf() - - init { - val testIntent = Intent(ACTION_SEND).apply { - type = intentType - flags = FLAG_ACTIVITY_NEW_TASK - } - - scope.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, - resolveInfo.activityInfo.name - ) - } - - 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 onDetachedFromRecyclerView(recyclerView: RecyclerView) { - super.onDetachedFromRecyclerView(recyclerView) - scope.cancel() - } -} - -class AppShareItemViewHolder( - itemView: View, - actionEmitter: Observer -) : RecyclerView.ViewHolder(itemView) { - - private var shareItem: ShareItem? = null - - init { - itemView.setOnClickListener { - shareItem?.let { - actionEmitter.onNext(ShareAction.ShareAppClicked(it)) - } - } - } - - internal fun bind(item: ShareItem) { - shareItem = item - itemView.appName.text = item.name - itemView.appIcon.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, val activityName: String) diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareCloseView.kt b/app/src/main/java/org/mozilla/fenix/share/ShareCloseView.kt new file mode 100644 index 000000000..b9a511cfc --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/share/ShareCloseView.kt @@ -0,0 +1,30 @@ +/* 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.ViewGroup +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.share_close.* +import org.mozilla.fenix.R + +/** + * Callbacks for possible user interactions on the [ShareCloseView] + */ +interface ShareCloseInteractor { + fun onShareClosed() +} + +class ShareCloseView( + override val containerView: ViewGroup, + private val interactor: ShareCloseInteractor +) : LayoutContainer { + init { + LayoutInflater.from(containerView.context) + .inflate(R.layout.share_close, containerView, true) + + closeButton.setOnClickListener { interactor.onShareClosed() } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareComponent.kt b/app/src/main/java/org/mozilla/fenix/share/ShareComponent.kt deleted file mode 100644 index 74e92f856..000000000 --- a/app/src/main/java/org/mozilla/fenix/share/ShareComponent.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* 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.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) : ShareAction() - data class ShareAppClicked(val item: ShareItem) : ShareAction() -} - -class ShareComponent( - private val container: ViewGroup, - bus: ActionBusFactory, - viewModelProvider: UIComponentViewModelProvider -) : UIComponent( - 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( - initialState, - reducer -) { - companion object { - val reducer: Reducer = { _, _ -> ShareState } - } -} 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 1531f87f1..3c51798a5 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt @@ -4,35 +4,25 @@ package org.mozilla.fenix.share -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.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatDialogFragment import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_share.view.* -import mozilla.components.concept.sync.DeviceEventOutgoing -import mozilla.components.concept.sync.OAuthAccount -import org.mozilla.fenix.FenixViewModelProvider -import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R -import org.mozilla.fenix.ext.nav -import org.mozilla.fenix.ext.requireComponents -import org.mozilla.fenix.mvi.ActionBusFactory -import org.mozilla.fenix.mvi.getAutoDisposeObservable class ShareFragment : AppCompatDialogFragment() { interface TabsSharedCallback { fun onTabsShared(tabsSize: Int) } - private lateinit var component: ShareComponent + private lateinit var shareInteractor: ShareInteractor + private lateinit var shareCloseView: ShareCloseView + private lateinit var shareToAccountDevicesView: ShareToAccountDevicesView + private lateinit var shareToAppsView: ShareToAppsView private var tabs: Array = emptyArray() override fun onCreate(savedInstanceState: Bundle?) { @@ -53,92 +43,13 @@ class ShareFragment : AppCompatDialogFragment() { tabs = args.tabs ?: arrayOf(ShareTab(args.url!!, args.title ?: "")) - component = ShareComponent( - view.shareWrapper, - ActionBusFactory.get(this), - FenixViewModelProvider.create( - this, - ShareUIViewModel::class.java - ) { - ShareUIViewModel(ShareState) - } - ) + shareInteractor = ShareInteractor() - return view - } - - override fun onResume() { - super.onResume() - subscribeToActions() - } - - @SuppressWarnings("ComplexMethod") - private fun subscribeToActions() { - getAutoDisposeObservable().subscribe { - when (it) { - ShareAction.Close -> { - dismiss() - } - ShareAction.SignInClicked -> { - val directions = - ShareFragmentDirections.actionShareFragmentToTurnOnSyncFragment() - nav(R.id.shareFragment, directions) - dismiss() - } - ShareAction.AddNewDeviceClicked -> { - context?.let { - AlertDialog.Builder(it).apply { - setMessage(R.string.sync_connect_device_dialog) - setPositiveButton(R.string.sync_confirmation_button) { dialog, _ -> dialog.cancel() } - create() - }.show() - } - } - is ShareAction.ShareDeviceClicked -> { - val authAccount = - requireComponents.backgroundServices.accountManager.authenticatedAccount() - authAccount?.run { - sendSendTab(this, it.device.id, tabs) - } - dismiss() - } - is ShareAction.SendAllClicked -> { - val authAccount = - requireComponents.backgroundServices.accountManager.authenticatedAccount() - authAccount?.run { - it.devices.forEach { device -> - sendSendTab(this, device.id, tabs) - } - } - dismiss() - } - is ShareAction.ShareAppClicked -> { - - val shareText = tabs.joinToString("\n") { tab -> tab.url } + shareCloseView = ShareCloseView(view.closeSharingLayout, shareInteractor) + shareToAccountDevicesView = ShareToAccountDevicesView(view.devicesShareLayout, shareInteractor) + shareToAppsView = ShareToAppsView(view.appsShareLayout, shareInteractor) - val intent = Intent(ACTION_SEND).apply { - putExtra(EXTRA_TEXT, shareText) - type = "text/plain" - flags = FLAG_ACTIVITY_NEW_TASK - setClassName(it.item.packageName, it.item.activityName) - } - startActivity(intent) - dismiss() - } - } - } - } - - private fun sendSendTab(account: OAuthAccount, deviceId: String, tabs: Array) { - account.run { - tabs.forEach { tab -> - deviceConstellation().sendEventToDeviceAsync( - deviceId, - DeviceEventOutgoing.SendTab(tab.title, tab.url) - ) - } - } - (activity as? HomeActivity)?.onTabsShared(tabs.size) + return view } } diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareInteractor.kt b/app/src/main/java/org/mozilla/fenix/share/ShareInteractor.kt new file mode 100644 index 000000000..8eebc74b2 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/share/ShareInteractor.kt @@ -0,0 +1,37 @@ +/* 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 mozilla.components.concept.sync.Device +import org.mozilla.fenix.share.listadapters.Application + +/** + * Interactor for the share screen. + */ +class ShareInteractor : ShareCloseInteractor, ShareToAccountDevicesInteractor, ShareToAppsInteractor { + override fun onShareClosed() { + TODO("not yet!? implemented") + } + + override fun onSignIn() { + TODO("not yet!? implemented") + } + + override fun onAddNewDevice() { + TODO("not yet!? implemented") + } + + override fun onShareToDevice(device: Device) { + TODO("not yet!? implemented") + } + + override fun onShareToAllDevices(devices: List) { + TODO("not yet!? implemented") + } + + override fun onShareToApp(appToShareTo: Application) { + 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 new file mode 100644 index 000000000..bdd510958 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/share/ShareToAccountDevicesView.kt @@ -0,0 +1,31 @@ +/* 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.ViewGroup +import kotlinx.android.extensions.LayoutContainer +import mozilla.components.concept.sync.Device +import org.mozilla.fenix.R + +/** + * Callbacks for possible user interactions on the [ShareToAccountDevicesView] + */ +interface ShareToAccountDevicesInteractor { + fun onSignIn() + fun onAddNewDevice() + fun onShareToDevice(device: Device) + fun onShareToAllDevices(devices: List) +} + +class ShareToAccountDevicesView( + override val containerView: ViewGroup, + private val interactor: ShareToAccountDevicesInteractor +) : LayoutContainer { + init { + LayoutInflater.from(containerView.context) + .inflate(R.layout.share_to_account_devices, containerView, true) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareToAppsView.kt b/app/src/main/java/org/mozilla/fenix/share/ShareToAppsView.kt new file mode 100644 index 000000000..39aeb1f58 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/share/ShareToAppsView.kt @@ -0,0 +1,28 @@ +/* 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.ViewGroup +import kotlinx.android.extensions.LayoutContainer +import org.mozilla.fenix.R +import org.mozilla.fenix.share.listadapters.Application + +/** + * Callbacks for possible user interactions on the [ShareCloseView] + */ +interface ShareToAppsInteractor { + fun onShareToApp(appToShareTo: Application) +} + +class ShareToAppsView( + override val containerView: ViewGroup, + private val interactor: ShareToAppsInteractor +) : LayoutContainer { + init { + LayoutInflater.from(containerView.context) + .inflate(R.layout.share_to_apps, containerView, true) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareUIView.kt b/app/src/main/java/org/mozilla/fenix/share/ShareUIView.kt deleted file mode 100644 index 30cf3a79f..000000000 --- a/app/src/main/java/org/mozilla/fenix/share/ShareUIView.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* 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.R -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.mvi.UIView - -class ShareUIView( - container: ViewGroup, - actionEmitter: Observer, - changesObservable: Observable -) : UIView( - 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() { - progressBar.visibility = View.GONE - appsList.visibility = View.VISIBLE - } - }) - } - appsList.adapter = adapter - - if (view.context.components.backgroundServices.accountManager.accountNeedsReauth()) { - sendTabGroup.visibility = View.GONE - accountHeaderText.visibility = View.GONE - } else { - devicesList.adapter = AccountDevicesShareAdapter(view.context, actionEmitter) - } - - container.setOnClickListener { actionEmitter.onNext(ShareAction.Close) } - closeButton.setOnClickListener { actionEmitter.onNext(ShareAction.Close) } - } - - override fun updateView() = Consumer { - ShareState - } -} diff --git a/app/src/main/java/org/mozilla/fenix/share/listadapters/AccountDevicesAdapter.kt b/app/src/main/java/org/mozilla/fenix/share/listadapters/AccountDevicesAdapter.kt new file mode 100644 index 000000000..215f51f07 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/share/listadapters/AccountDevicesAdapter.kt @@ -0,0 +1,45 @@ +/* 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.listadapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import mozilla.components.concept.sync.Device +import org.mozilla.fenix.share.ShareToAccountDevicesInteractor +import org.mozilla.fenix.share.viewholders.AccountDeviceViewHolder + +class AccountDevicesShareAdapter( + private val interactor: ShareToAccountDevicesInteractor, + private val devices: MutableList = mutableListOf() +) : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountDeviceViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(AccountDeviceViewHolder.LAYOUT_ID, parent, false) + + return AccountDeviceViewHolder(view, interactor) + } + + override fun getItemCount(): Int = devices.size + + override fun onBindViewHolder(holder: AccountDeviceViewHolder, position: Int) { + holder.bind(devices[position]) + } + + fun updateData(deviceOptions: List) { + this.devices.clear() + this.devices.addAll(deviceOptions) + notifyDataSetChanged() + } +} + +sealed class SyncShareOption { + object SignIn : SyncShareOption() + object AddNewDevice : SyncShareOption() + data class SendAll(val devices: List) : SyncShareOption() + data class Mobile(val name: String, val device: Device) : SyncShareOption() + data class Desktop(val name: String, val device: Device) : SyncShareOption() +} diff --git a/app/src/main/java/org/mozilla/fenix/share/listadapters/AppShareAdapter.kt b/app/src/main/java/org/mozilla/fenix/share/listadapters/AppShareAdapter.kt new file mode 100644 index 000000000..e2584983c --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/share/listadapters/AppShareAdapter.kt @@ -0,0 +1,44 @@ +/* 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.listadapters + +import android.graphics.drawable.Drawable +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.mozilla.fenix.share.ShareToAppsInteractor +import org.mozilla.fenix.share.viewholders.AppViewHolder + +class AppShareAdapter( + private val interactor: ShareToAppsInteractor, + private val applications: MutableList = mutableListOf() +) : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(AppViewHolder.LAYOUT_ID, parent, false) + + return AppViewHolder(view, interactor) + } + + override fun getItemCount(): Int = applications.size + + override fun onBindViewHolder(holder: AppViewHolder, position: Int) { + holder.bind(applications[position]) + } + + fun updateData(applications: List) { + this.applications.clear() + this.applications.addAll(applications) + notifyDataSetChanged() + } +} + +data class AppShareOption( + val name: String, + val icon: Drawable, + val packageName: String, + val activityName: String +) diff --git a/app/src/main/java/org/mozilla/fenix/share/viewholders/AccountDeviceViewHolder.kt b/app/src/main/java/org/mozilla/fenix/share/viewholders/AccountDeviceViewHolder.kt new file mode 100644 index 000000000..de61568db --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/share/viewholders/AccountDeviceViewHolder.kt @@ -0,0 +1,82 @@ +/* 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.viewholders + +import android.content.Context +import android.graphics.PorterDuff +import android.view.View +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.account_share_list_item.view.* +import org.mozilla.fenix.R +import org.mozilla.fenix.lib.Do +import org.mozilla.fenix.share.ShareToAccountDevicesInteractor +import org.mozilla.fenix.share.listadapters.SyncShareOption + +class AccountDeviceViewHolder( + itemView: View, + private val interactor: ShareToAccountDevicesInteractor +) : RecyclerView.ViewHolder(itemView) { + + private val context: Context = itemView.context + + fun bind(option: SyncShareOption) { + bindClickListeners(option) + bindView(option) + } + + private fun bindClickListeners(option: SyncShareOption) { + itemView.setOnClickListener { + Do exhaustive when (option) { + SyncShareOption.SignIn -> interactor.onSignIn() + SyncShareOption.AddNewDevice -> interactor.onAddNewDevice() + is SyncShareOption.SendAll -> interactor.onShareToAllDevices(option.devices) + is SyncShareOption.Mobile -> interactor.onShareToDevice(option.device) + is SyncShareOption.Desktop -> interactor.onShareToDevice(option.device) + } + } + } + + private fun bindView(option: SyncShareOption) { + val (name, drawableRes, colorRes) = when (option) { + SyncShareOption.SignIn -> Triple( + context.getText(R.string.sync_sign_in), + R.drawable.mozac_ic_sync, + R.color.default_share_background + ) + SyncShareOption.AddNewDevice -> Triple( + context.getText(R.string.sync_connect_device), + R.drawable.mozac_ic_new, + R.color.default_share_background + ) + is SyncShareOption.SendAll -> Triple( + context.getText(R.string.sync_send_to_all), + R.drawable.mozac_ic_select_all, + R.color.default_share_background + ) + is SyncShareOption.Mobile -> Triple( + option.name, + R.drawable.mozac_ic_device_mobile, + R.color.device_type_mobile_background + ) + is SyncShareOption.Desktop -> Triple( + option.name, + R.drawable.mozac_ic_device_desktop, + R.color.device_type_desktop_background + ) + } + + itemView.deviceIcon.apply { + setImageResource(drawableRes) + background.setColorFilter(ContextCompat.getColor(context, colorRes), PorterDuff.Mode.SRC_IN) + drawable.setTint(ContextCompat.getColor(context, R.color.device_foreground)) + } + itemView.deviceName.text = name + } + + companion object { + const val LAYOUT_ID = R.layout.account_share_list_item + } +} diff --git a/app/src/main/java/org/mozilla/fenix/share/viewholders/AppViewHolder.kt b/app/src/main/java/org/mozilla/fenix/share/viewholders/AppViewHolder.kt new file mode 100644 index 000000000..1e2423182 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/share/viewholders/AppViewHolder.kt @@ -0,0 +1,38 @@ +/* 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.viewholders + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.app_share_list_item.view.* +import org.mozilla.fenix.R +import org.mozilla.fenix.share.ShareToAppsInteractor +import org.mozilla.fenix.share.listadapters.AppShareOption + +class AppViewHolder( + itemView: View, + interactor: ShareToAppsInteractor +) : RecyclerView.ViewHolder(itemView) { + + private var application: AppShareOption? = null + + init { + itemView.setOnClickListener { + application?.let { application -> + interactor.onShareToApp(application) + } + } + } + + fun bind(item: AppShareOption) { + application = item + itemView.appName.text = item.name + itemView.appIcon.setImageDrawable(item.icon) + } + + companion object { + const val LAYOUT_ID = R.layout.app_share_list_item + } +} diff --git a/app/src/main/res/layout/component_share.xml b/app/src/main/res/layout/component_share.xml deleted file mode 100644 index b9b7448c4..000000000 --- a/app/src/main/res/layout/component_share.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_share.xml b/app/src/main/res/layout/fragment_share.xml index aaf2e58d4..21fa1c3f7 100644 --- a/app/src/main/res/layout/fragment_share.xml +++ b/app/src/main/res/layout/fragment_share.xml @@ -3,12 +3,57 @@ - 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/. --> - + tools:context="org.mozilla.fenix.share.ShareFragment"> + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/share_close.xml b/app/src/main/res/layout/share_close.xml new file mode 100644 index 000000000..e79bcee1a --- /dev/null +++ b/app/src/main/res/layout/share_close.xml @@ -0,0 +1,24 @@ + + + + diff --git a/app/src/main/res/layout/share_to_account_devices.xml b/app/src/main/res/layout/share_to_account_devices.xml new file mode 100644 index 000000000..61cde4059 --- /dev/null +++ b/app/src/main/res/layout/share_to_account_devices.xml @@ -0,0 +1,41 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/share_to_apps.xml b/app/src/main/res/layout/share_to_apps.xml new file mode 100644 index 000000000..b21e0a446 --- /dev/null +++ b/app/src/main/res/layout/share_to_apps.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + +