From fdbf63fb97a03fed5961c985c2d46e5a66022185 Mon Sep 17 00:00:00 2001 From: Mihai Branescu Date: Tue, 26 Nov 2019 17:39:54 +0200 Subject: [PATCH] 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 --- app/build.gradle | 2 + .../fenix/ui/robots/ThreeDotMenuMainRobot.kt | 2 +- .../mozilla/fenix/share/ShareController.kt | 8 ++ .../org/mozilla/fenix/share/ShareFragment.kt | 15 +++- .../mozilla/fenix/share/ShareToAppsView.kt | 19 ++++- .../org/mozilla/fenix/share/ShareViewModel.kt | 39 +++++++++- .../res/drawable/recent_apps_background.xml | 8 ++ app/src/main/res/layout/share_to_apps.xml | 78 +++++++++++++------ app/src/main/res/values/dimens.xml | 12 +++ app/src/main/res/values/styles.xml | 7 ++ .../mozilla/fenix/share/ShareViewModelTest.kt | 2 +- buildSrc/src/main/java/Dependencies.kt | 1 + 12 files changed, 162 insertions(+), 31 deletions(-) create mode 100644 app/src/main/res/drawable/recent_apps_background.xml diff --git a/app/build.gradle b/app/build.gradle index cde36dc15..70ec18313 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt index 36b984d76..89e279f76 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt @@ -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() diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareController.kt b/app/src/main/java/org/mozilla/fenix/share/ShareController.kt index a28b8f3a9..3eaf6bd29 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareController.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareController.kt @@ -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(", ")) 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 7ffebbda9..552e57f26 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt @@ -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) + } } /** diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareToAppsView.kt b/app/src/main/java/org/mozilla/fenix/share/ShareToAppsView.kt index 7a15297fd..aa70aac28 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareToAppsView.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareToAppsView.kt @@ -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) { progressBar.visibility = View.GONE - appsList.visibility = View.VISIBLE + appsList.visibility = View.VISIBLE adapter.submitList(targets) } + + fun setRecentShareTargets(recentTargets: List) { + if (recentTargets.isEmpty()) { + return + } + progressBar.visibility = View.GONE + + recentAppsContainer.visibility = View.VISIBLE + recentAdapter.submitList(recentTargets) + } } diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareViewModel.kt b/app/src/main/java/org/mozilla/fenix/share/ShareViewModel.kt index 0cbbf4b66..3228abc98 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareViewModel.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareViewModel.kt @@ -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() } private val fxaAccountManager = application.components.backgroundServices.accountManager + private val recentAppsStorage = RecentAppsStorage(application.applicationContext) private val devicesListLiveData = MutableLiveData>(emptyList()) private val appsListLiveData = MutableLiveData>(emptyList()) + private val recentAppsListLiveData = MutableLiveData>(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> get() = appsListLiveData + /** + * List of recent applications that can be shared to. + */ + val recentAppsList: LiveData> 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, + recentApps: List + ): List { + return apps.filter { app -> !recentApps.contains(app) } + } + + @WorkerThread + internal fun buildRecentAppsList(apps: List): List { + val recentAppsDatabase = recentAppsStorage.getRecentAppsUpTo(RECENT_APPS_LIMIT) + val result: MutableList = 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. */ diff --git a/app/src/main/res/drawable/recent_apps_background.xml b/app/src/main/res/drawable/recent_apps_background.xml new file mode 100644 index 000000000..a7c314420 --- /dev/null +++ b/app/src/main/res/drawable/recent_apps_background.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/share_to_apps.xml b/app/src/main/res/layout/share_to_apps.xml index d44d1f5af..c89e27747 100644 --- a/app/src/main/res/layout/share_to_apps.xml +++ b/app/src/main/res/layout/share_to_apps.xml @@ -1,10 +1,8 @@ - - - @@ -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" /> - + + + + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 80f44aacc..9aa5597e5 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -115,4 +115,16 @@ 8dp 4dp + + 10dp + 8dp + 8dp + 16dp + 160dp + 8dp + 16dp + 8dp + 12sp + 16dp + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 6aac14764..9b7d8ee22 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -361,7 +361,14 @@ 40dp 32dp ?accentUsedOnDarkBackground + +