Commit fdbf63fb authored by Mihai Branescu's avatar Mihai Branescu Committed by Emily Kager
Browse files

For #4231

Added kapt plugin + dependencies in order to be able to use Room
Added recent apps to share fragment (top 6)
Extracted dimens of share_to_apps.xml in the dimens file
parent 3486f30b
......@@ -5,6 +5,7 @@ plugins {
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'jacoco'
apply from: "$project.rootDir/automation/gradle/versionCode.gradle"
apply plugin: 'androidx.navigation.safeargs.kotlin'
......@@ -436,6 +437,7 @@ dependencies {
implementation Deps.mozilla_feature_readerview
implementation Deps.mozilla_feature_tab_collections
implementation Deps.mozilla_feature_top_sites
implementation Deps.mozilla_feature_share
implementation Deps.mozilla_feature_accounts_push
implementation Deps.mozilla_feature_webcompat
implementation Deps.mozilla_feature_webnotifications
......
......@@ -293,7 +293,7 @@ private fun assertSendToDeviceTitle() = SendToDeviceTitle()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun ShareALinkTitle() =
onView(allOf(withText("SHARE A LINK"), withResourceName("link_header")))
onView(allOf(withText(R.string.share_link_all_apps_subheader), withResourceName("apps_link_header")))
private fun assertShareALinkTitle() = ShareALinkTitle()
......
......@@ -14,6 +14,7 @@ import android.net.Uri
import androidx.annotation.VisibleForTesting
import androidx.navigation.NavController
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
......@@ -22,6 +23,7 @@ import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.concept.sync.Device
import mozilla.components.concept.sync.TabData
import mozilla.components.feature.accounts.push.SendTabUseCases
import mozilla.components.feature.share.RecentAppsStorage
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.metrics.Event
......@@ -65,6 +67,8 @@ class DefaultShareController(
private val sendTabUseCases: SendTabUseCases,
private val snackbar: FenixSnackbar,
private val navController: NavController,
private val recentAppsStorage: RecentAppsStorage,
private val lifecycleScope: CoroutineScope,
private val dismiss: (ShareController.Result) -> Unit
) : ShareController {
......@@ -79,6 +83,10 @@ class DefaultShareController(
}
override fun handleShareToApp(app: AppShareOption) {
lifecycleScope.launch(Dispatchers.IO) {
recentAppsStorage.updateRecentApp(app.packageName)
}
val intent = Intent(ACTION_SEND).apply {
putExtra(EXTRA_TEXT, getShareText())
putExtra(EXTRA_SUBJECT, shareData.map { it.title }.joinToString(", "))
......
......@@ -12,14 +12,20 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.observe
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.fragment_share.view.*
import kotlinx.android.synthetic.main.fragment_share.view.appsShareLayout
import kotlinx.android.synthetic.main.fragment_share.view.closeSharingContent
import kotlinx.android.synthetic.main.fragment_share.view.closeSharingScrim
import kotlinx.android.synthetic.main.fragment_share.view.devicesShareLayout
import kotlinx.android.synthetic.main.fragment_share.view.shareWrapper
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.selector.findTabOrCustomTab
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.feature.accounts.push.SendTabUseCases
import mozilla.components.feature.share.RecentAppsStorage
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.ext.getRootView
......@@ -67,7 +73,9 @@ class ShareFragment : AppCompatDialogFragment() {
shareData = shareData,
snackbar = FenixSnackbar.makeWithToolbarPadding(requireActivity().getRootView()!!),
navController = findNavController(),
sendTabUseCases = SendTabUseCases(accountManager)
sendTabUseCases = SendTabUseCases(accountManager),
recentAppsStorage = RecentAppsStorage(requireContext()),
lifecycleScope = lifecycleScope
) { result ->
consumePrompt {
when (result) {
......@@ -108,6 +116,9 @@ class ShareFragment : AppCompatDialogFragment() {
viewModel.appsList.observe(viewLifecycleOwner) { appsToShareTo ->
shareToAppsView.setShareTargets(appsToShareTo)
}
viewModel.recentAppsList.observe(viewLifecycleOwner) { appsToShareTo ->
shareToAppsView.setRecentShareTargets(appsToShareTo)
}
}
/**
......
......@@ -8,7 +8,10 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.share_to_apps.*
import kotlinx.android.synthetic.main.share_to_apps.appsList
import kotlinx.android.synthetic.main.share_to_apps.progressBar
import kotlinx.android.synthetic.main.share_to_apps.recentAppsContainer
import kotlinx.android.synthetic.main.share_to_apps.recentAppsList
import org.mozilla.fenix.R
import org.mozilla.fenix.share.listadapters.AppShareAdapter
import org.mozilla.fenix.share.listadapters.AppShareOption
......@@ -26,18 +29,30 @@ class ShareToAppsView(
) : LayoutContainer {
private val adapter = AppShareAdapter(interactor)
private val recentAdapter = AppShareAdapter(interactor)
init {
LayoutInflater.from(containerView.context)
.inflate(R.layout.share_to_apps, containerView, true)
appsList.adapter = adapter
recentAppsList.adapter = recentAdapter
}
fun setShareTargets(targets: List<AppShareOption>) {
progressBar.visibility = View.GONE
appsList.visibility = View.VISIBLE
appsList.visibility = View.VISIBLE
adapter.submitList(targets)
}
fun setRecentShareTargets(recentTargets: List<AppShareOption>) {
if (recentTargets.isEmpty()) {
return
}
progressBar.visibility = View.GONE
recentAppsContainer.visibility = View.VISIBLE
recentAdapter.submitList(recentTargets)
}
}
......@@ -21,6 +21,7 @@ import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers.IO
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.ext.components
import org.mozilla.fenix.ext.isOnline
......@@ -29,11 +30,17 @@ import org.mozilla.fenix.share.listadapters.SyncShareOption
class ShareViewModel(application: Application) : AndroidViewModel(application) {
companion object {
private const val RECENT_APPS_LIMIT = 6
}
private val connectivityManager by lazy { application.getSystemService<ConnectivityManager>() }
private val fxaAccountManager = application.components.backgroundServices.accountManager
private val recentAppsStorage = RecentAppsStorage(application.applicationContext)
private val devicesListLiveData = MutableLiveData<List<SyncShareOption>>(emptyList())
private val appsListLiveData = MutableLiveData<List<AppShareOption>>(emptyList())
private val recentAppsListLiveData = MutableLiveData<List<AppShareOption>>(emptyList())
@VisibleForTesting
internal val networkCallback = object : ConnectivityManager.NetworkCallback() {
......@@ -61,6 +68,10 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) {
* List of applications that can be shared to.
*/
val appsList: LiveData<List<AppShareOption>> get() = appsListLiveData
/**
* List of recent applications that can be shared to.
*/
val recentAppsList: LiveData<List<AppShareOption>> get() = recentAppsListLiveData
/**
* Load a list of devices and apps into [devicesList] and [appsList].
......@@ -77,7 +88,12 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
val shareAppsActivities = getIntentActivities(shareIntent, getApplication())
val apps = buildAppsList(shareAppsActivities, getApplication())
var apps = buildAppsList(shareAppsActivities, getApplication())
recentAppsStorage.updateDatabaseWithNewApps(apps.map { app -> app.packageName })
val recentApps = buildRecentAppsList(apps)
apps = filterOutRecentApps(apps, recentApps)
recentAppsListLiveData.postValue(recentApps)
appsListLiveData.postValue(apps)
}
......@@ -87,6 +103,27 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) {
}
}
private fun filterOutRecentApps(
apps: List<AppShareOption>,
recentApps: List<AppShareOption>
): List<AppShareOption> {
return apps.filter { app -> !recentApps.contains(app) }
}
@WorkerThread
internal fun buildRecentAppsList(apps: List<AppShareOption>): List<AppShareOption> {
val recentAppsDatabase = recentAppsStorage.getRecentAppsUpTo(RECENT_APPS_LIMIT)
val result: MutableList<AppShareOption> = ArrayList()
for (recentApp in recentAppsDatabase) {
for (app in apps) {
if (recentApp.packageName == app.packageName) {
result.add(app)
}
}
}
return result
}
/**
* Unregisters the network callback and cleans up.
*/
......
<?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">
<solid android:color="?inset"/>
<corners android:radius="@dimen/share_recent_apps_background_radius"/>
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
<?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"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
......@@ -13,43 +11,75 @@
android:id="@+id/progressBar"
android:layout_width="76dp"
android:layout_height="37dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_marginTop="@dimen/share_progress_bar_margin"
android:layout_marginBottom="@dimen/share_progress_bar_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/link_header" />
app:layout_constraintTop_toBottomOf="@id/apps_link_header" />
<TextView
android:id="@+id/link_header"
<LinearLayout
android:id="@+id/recentAppsContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:singleLine="true"
android:text="@string/share_link_subheader"
android:textAllCaps="true"
android:textColor="?secondaryText"
android:textSize="12sp"
android:textStyle="bold"
android:layout_marginStart="@dimen/share_recent_apps_margin"
android:layout_marginTop="@dimen/share_recent_apps_margin"
android:layout_marginBottom="@dimen/share_recent_apps_margin"
android:background="@drawable/recent_apps_background"
android:orientation="vertical"
android:paddingStart="@dimen/share_recent_apps_padding"
android:paddingTop="@dimen/share_recent_apps_padding"
android:paddingEnd="@dimen/share_recent_apps_padding"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/recent_apps_link_header"
style="@style/ShareHeaderTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/share_link_recent_apps_subheader" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recentAppsList"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:clipToPadding="false"
android:minHeight="@dimen/share_list_min_height"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2" />
</LinearLayout>
<TextView
android:id="@+id/apps_link_header"
style="@style/ShareHeaderTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/share_all_apps_header_margin"
android:layout_marginTop="@dimen/share_all_apps_header_margin"
android:text="@string/share_link_all_apps_subheader"
app:layout_constraintStart_toEndOf="@+id/recentAppsContainer"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/appsList"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:minHeight="160dp"
android:layout_marginBottom="8dp"
android:layout_marginBottom="@dimen/share_all_apps_list_margin"
android:clipToPadding="false"
android:minHeight="@dimen/share_list_min_height"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="8dp"
android:paddingStart="@dimen/share_all_apps_list_padding_start"
android:paddingEnd="@dimen/share_all_apps_list_padding_end"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/link_header"
app:layout_constraintStart_toEndOf="@+id/recentAppsContainer"
app:layout_constraintTop_toBottomOf="@id/apps_link_header"
app:spanCount="2" />
</androidx.constraintlayout.widget.ConstraintLayout>
......@@ -115,4 +115,16 @@
<dimen name="migration_progress_margin_vertical">8dp</dimen>
<dimen name="migration_progress_margin_start">4dp</dimen>
<!-- Share Fragment -->
<dimen name="share_recent_apps_background_radius">10dp</dimen>
<dimen name="share_recent_apps_padding">8dp</dimen>
<dimen name="share_recent_apps_margin">8dp</dimen>
<dimen name="share_all_apps_header_margin">16dp</dimen>
<dimen name="share_list_min_height">160dp</dimen>
<dimen name="share_all_apps_list_margin">8dp</dimen>
<dimen name="share_all_apps_list_padding_start">16dp</dimen>
<dimen name="share_all_apps_list_padding_end">8dp</dimen>
<dimen name="share_header_text_size">12sp</dimen>
<dimen name="share_progress_bar_margin">16dp</dimen>
</resources>
......@@ -361,7 +361,14 @@
<item name="android:minHeight">40dp</item>
<item name="android:layout_marginTop">32dp</item>
<item name="android:textColor">?accentUsedOnDarkBackground</item>
</style>
<style name="ShareHeaderTextStyle">
<item name="android:singleLine">true</item>
<item name="android:textAllCaps">true</item>
<item name="android:textColor">?secondaryText</item>
<item name="android:textSize">@dimen/share_header_text_size</item>
<item name="android:textStyle">bold</item>
</style>
<style name="FirefoxAccountsDialogStyle" parent="DialogStyleBase">
......
......@@ -13,9 +13,9 @@ import android.net.ConnectivityManager
import androidx.core.content.getSystemService
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.spyk
import io.mockk.verify
import io.mockk.mockkStatic
import kotlinx.coroutines.runBlocking
import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.support.test.robolectric.testContext
......
......@@ -126,6 +126,7 @@ object Deps {
const val mozilla_feature_tab_collections = "org.mozilla.components:feature-tab-collections:${Versions.mozilla_android_components}"
const val mozilla_feature_accounts_push = "org.mozilla.components:feature-accounts-push:${Versions.mozilla_android_components}"
const val mozilla_feature_top_sites = "org.mozilla.components:feature-top-sites:${Versions.mozilla_android_components}"
const val mozilla_feature_share = "org.mozilla.components:feature-share:${Versions.mozilla_android_components}"
const val mozilla_feature_webcompat = "org.mozilla.components:feature-webcompat:${Versions.mozilla_android_components}"
const val mozilla_feature_webnotifications = "org.mozilla.components:feature-webnotifications:${Versions.mozilla_android_components}"
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment