From b8304f1c1c4dde43e597ce9ced9d9d5dcc1d6dcc Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Wed, 26 Jan 2022 21:51:35 +0100 Subject: [PATCH] Fix permission banners in search results --- i18n/src/main/res/values-de/strings.xml | 1 + i18n/src/main/res/values/strings.xml | 2 + .../data/search/MissingPermission.kt | 7 +- .../ui/legacy/component/CalendarView.kt | 102 +++++++++++++----- .../ui/legacy/component/ContactView.kt | 101 ++++++++++++----- .../launcher2/ui/legacy/component/FileView.kt | 98 +++++++++++++---- .../search/PermissionListRepresentation.kt | 43 ++++++-- .../main/res/layout/view_permission_list.xml | 33 +----- 8 files changed, 270 insertions(+), 117 deletions(-) diff --git a/i18n/src/main/res/values-de/strings.xml b/i18n/src/main/res/values-de/strings.xml index 2ab22b18..d059f51d 100644 --- a/i18n/src/main/res/values-de/strings.xml +++ b/i18n/src/main/res/values-de/strings.xml @@ -477,4 +477,5 @@ Sie haben noch kein Microsoft-Konto verbunden Sie haben noch kein Google-Konto verbunden Konto verbinden + Deaktivieren \ No newline at end of file diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 711dc517..2aedad7a 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -516,4 +516,6 @@ You haven\'t connected a Microsoft account yet You haven\'t connected a Google account yet Connect account + + Turn off diff --git a/permissions/src/main/java/de/mm20/launcher2/data/search/MissingPermission.kt b/permissions/src/main/java/de/mm20/launcher2/data/search/MissingPermission.kt index 0de76f94..8c39c7b7 100644 --- a/permissions/src/main/java/de/mm20/launcher2/data/search/MissingPermission.kt +++ b/permissions/src/main/java/de/mm20/launcher2/data/search/MissingPermission.kt @@ -7,7 +7,12 @@ import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.R -class MissingPermission(override val label: String, val permissionGroup: PermissionGroup): Searchable() { +class MissingPermission( + override val label: String, + val permissionGroup: PermissionGroup, + val secondaryActionLabel: String? = null, + val secondaryAction: (() -> Unit)? = null + ): Searchable() { override val key: String get() = "permission://${permissionGroup.ordinal}" diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt index 46df4e98..e9b93051 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt @@ -8,15 +8,19 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.asLiveData +import de.mm20.launcher2.ktx.lifecycleScope import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionsManager -import de.mm20.launcher2.preferences.LauncherPreferences -import de.mm20.launcher2.search.data.CalendarEvent +import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.search.data.MissingPermission import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.launcher.search.SearchVM import de.mm20.launcher2.ui.legacy.search.SearchListView +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.get @@ -30,40 +34,84 @@ class CalendarView : FrameLayout, KoinComponent { defStyleRes ) - private val calendarEvents: LiveData?> - init { val permissionsManager: PermissionsManager = get() + val dataStore: LauncherDataStore = get() View.inflate(context, R.layout.view_search_category_list, this) layoutTransition = LayoutTransition() layoutTransition.enableTransitionType(LayoutTransition.CHANGING) val card = findViewById(R.id.card) card.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) - val list = findViewById(R.id.list) val viewModel: SearchVM by (context as AppCompatActivity).viewModels() - calendarEvents = viewModel.calendarResults - calendarEvents.observe(context as AppCompatActivity, { - if (it == null) { - visibility = View.GONE - return@observe + + val showMissingPermissionBanner = combine( + dataStore.data.map { it.calendarSearch.enabled }, + permissionsManager.hasPermission(PermissionGroup.Calendar) + ) { calendarSearchEnabled, hasPermission -> + !hasPermission && calendarSearchEnabled + }.asLiveData() + + val searchQuery = viewModel.searchQuery + val calendarResults = viewModel.calendarResults + + val show = MediatorLiveData() + show.addSource(showMissingPermissionBanner) { + show.value = !searchQuery.value.isNullOrBlank() && + (showMissingPermissionBanner.value == true || !calendarResults.value.isNullOrEmpty()) + } + show.addSource(calendarResults) { + show.value = !searchQuery.value.isNullOrBlank() && + (showMissingPermissionBanner.value == true || !calendarResults.value.isNullOrEmpty()) + } + show.addSource(searchQuery) { + show.value = !searchQuery.value.isNullOrBlank() && + (showMissingPermissionBanner.value == true || !calendarResults.value.isNullOrEmpty()) + } + + show.observe(context as AppCompatActivity) { + visibility = if (it) { + View.VISIBLE + } else { + View.GONE } - if (it.isEmpty() && LauncherPreferences.instance.searchCalendars && !permissionsManager.checkPermissionOnce( - PermissionGroup.Calendar - ) - ) { - visibility = View.VISIBLE - list.submitItems( - listOf( - MissingPermission( - context.getString(R.string.permission_calendar_search), - PermissionGroup.Calendar - ) + } + + val list = findViewById(R.id.list) + calendarResults.observe(context as AppCompatActivity) { + if (showMissingPermissionBanner.value == true) { + list.submitItems(listOf( + MissingPermission( + context.getString(R.string.permission_calendar_search), + PermissionGroup.Calendar, + secondaryActionLabel = context.getString(R.string.turn_off), + secondaryAction = { + lifecycleScope.launch { + dataStore.updateData { + it.toBuilder() + .setCalendarSearch(it.calendarSearch.toBuilder().setEnabled(false)) + .build() + } + } + } ) - ) - return@observe + ) + it) + } else { + list.submitItems(it) } - visibility = if (it.isEmpty()) View.GONE else View.VISIBLE - list.submitItems(it) - }) + } + + showMissingPermissionBanner.observe(context as AppCompatActivity) { + if (it == true) { + list.submitItems(listOf( + MissingPermission( + context.getString(R.string.permission_calendar_search), + PermissionGroup.Calendar, + secondaryActionLabel = context.getString(R.string.turn_off) + ) + ) + calendarResults.value!!) + } else { + list.submitItems(calendarResults.value) + } + } } } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt index 758ff0e8..7b603913 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt @@ -8,20 +8,23 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.asLiveData +import de.mm20.launcher2.ktx.lifecycleScope import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionsManager -import de.mm20.launcher2.preferences.LauncherPreferences -import de.mm20.launcher2.search.data.Contact +import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.search.data.MissingPermission import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.launcher.search.SearchVM import de.mm20.launcher2.ui.legacy.search.SearchListView +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.get class ContactView : FrameLayout, KoinComponent { - private val contacts: LiveData?> constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) @@ -33,36 +36,82 @@ class ContactView : FrameLayout, KoinComponent { init { val permissionsManager: PermissionsManager = get() + val dataStore: LauncherDataStore = get() View.inflate(context, R.layout.view_search_category_list, this) layoutTransition = LayoutTransition() layoutTransition.enableTransitionType(LayoutTransition.CHANGING) val card = findViewById(R.id.card) card.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) val viewModel: SearchVM by (context as AppCompatActivity).viewModels() - contacts = viewModel.contactResults + + val showMissingPermissionBanner = combine( + dataStore.data.map { it.contactsSearch.enabled }, + permissionsManager.hasPermission(PermissionGroup.Contacts) + ) { contactSearchEnabled, hasPermission -> + !hasPermission && contactSearchEnabled + }.asLiveData() + + val searchQuery = viewModel.searchQuery + val contactResults = viewModel.contactResults + + val show = MediatorLiveData() + show.addSource(showMissingPermissionBanner) { + show.value = !searchQuery.value.isNullOrBlank() && + (showMissingPermissionBanner.value == true || !contactResults.value.isNullOrEmpty()) + } + show.addSource(contactResults) { + show.value = !searchQuery.value.isNullOrBlank() && + (showMissingPermissionBanner.value == true || !contactResults.value.isNullOrEmpty()) + } + show.addSource(searchQuery) { + show.value = !searchQuery.value.isNullOrBlank() && + (showMissingPermissionBanner.value == true || !contactResults.value.isNullOrEmpty()) + } + + show.observe(context as AppCompatActivity) { + visibility = if (it) { + View.VISIBLE + } else { + View.GONE + } + } + val list = findViewById(R.id.list) - contacts.observe(context as AppCompatActivity, { - if (it == null) { - visibility = View.GONE - return@observe - } - if (it.isEmpty() && LauncherPreferences.instance.searchContacts && !permissionsManager.checkPermissionOnce( - PermissionGroup.Contacts - ) - ) { - visibility = View.VISIBLE - list.submitItems( - listOf( - MissingPermission( - context.getString(R.string.permission_contact_search), - PermissionGroup.Contacts - ) + contactResults.observe(context as AppCompatActivity) { + if (showMissingPermissionBanner.value == true) { + list.submitItems(listOf( + MissingPermission( + context.getString(R.string.permission_contact_search), + PermissionGroup.Contacts, + secondaryActionLabel = context.getString(R.string.turn_off), + secondaryAction = { + lifecycleScope.launch { + dataStore.updateData { + it.toBuilder() + .setContactsSearch(it.contactsSearch.toBuilder().setEnabled(false)) + .build() + } + } + } ) - ) - return@observe + ) + it) + } else { + list.submitItems(it) } - visibility = if (it.isEmpty()) View.GONE else View.VISIBLE - list.submitItems(it) - }) + } + + showMissingPermissionBanner.observe(context as AppCompatActivity) { + if (it == true) { + list.submitItems(listOf( + MissingPermission( + context.getString(R.string.permission_contact_search), + PermissionGroup.Contacts, + secondaryActionLabel = context.getString(R.string.turn_off) + ) + ) + contactResults.value!!) + } else { + list.submitItems(contactResults.value) + } + } } } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt index 13ccd956..b49e194e 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt @@ -9,18 +9,24 @@ import android.widget.FrameLayout import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.asLiveData +import de.mm20.launcher2.ktx.lifecycleScope import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionsManager +import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.search.data.File import de.mm20.launcher2.search.data.MissingPermission import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.launcher.search.SearchVM import de.mm20.launcher2.ui.legacy.search.SearchListView +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.get class FileView : FrameLayout, KoinComponent { - private val files: LiveData?> constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) @@ -32,36 +38,82 @@ class FileView : FrameLayout, KoinComponent { init { val permissionsManager: PermissionsManager = get() + val dataStore: LauncherDataStore = get() View.inflate(context, R.layout.view_search_category_list, this) layoutTransition = LayoutTransition() layoutTransition.enableTransitionType(LayoutTransition.CHANGING) val card = findViewById(R.id.card) card.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) - val list = findViewById(R.id.list) val viewModel: SearchVM by (context as AppCompatActivity).viewModels() - files = viewModel.fileResults - files.observe(context as AppCompatActivity, { - if (it == null) { - visibility = View.GONE - return@observe + + val showMissingPermissionBanner = combine( + dataStore.data.map { it.fileSearch.localFiles }, + permissionsManager.hasPermission(PermissionGroup.ExternalStorage) + ) { localFileSearchEnabled, hasPermission -> + !hasPermission && localFileSearchEnabled + }.asLiveData() + + val searchQuery = viewModel.searchQuery + val fileResults = viewModel.fileResults + + val show = MediatorLiveData() + show.addSource(showMissingPermissionBanner) { + show.value = !searchQuery.value.isNullOrBlank() && + (showMissingPermissionBanner.value == true || !fileResults.value.isNullOrEmpty()) + } + show.addSource(fileResults) { + show.value = !searchQuery.value.isNullOrBlank() && + (showMissingPermissionBanner.value == true || !fileResults.value.isNullOrEmpty()) + } + show.addSource(searchQuery) { + show.value = !searchQuery.value.isNullOrBlank() && + (showMissingPermissionBanner.value == true || !fileResults.value.isNullOrEmpty()) + } + + show.observe(context as AppCompatActivity) { + visibility = if (it) { + View.VISIBLE + } else { + View.GONE } - if (it.isEmpty() && !permissionsManager.checkPermissionOnce( - PermissionGroup.ExternalStorage - ) - ) { - visibility = View.VISIBLE - list.submitItems( - listOf( - MissingPermission( - context.getString(R.string.permission_files_search), - PermissionGroup.ExternalStorage - ) + } + + val list = findViewById(R.id.list) + fileResults.observe(context as AppCompatActivity) { + if (showMissingPermissionBanner.value == true) { + list.submitItems(listOf( + MissingPermission( + context.getString(R.string.permission_files_search), + PermissionGroup.ExternalStorage, + secondaryActionLabel = context.getString(R.string.turn_off), + secondaryAction = { + lifecycleScope.launch { + dataStore.updateData { + it.toBuilder() + .setFileSearch(it.fileSearch.toBuilder().setLocalFiles(false)) + .build() + } + } + } ) - ) - return@observe + ) + it) + } else { + list.submitItems(it) } - visibility = if (it.isEmpty()) View.GONE else View.VISIBLE - list.submitItems(it) - }) + } + + showMissingPermissionBanner.observe(context as AppCompatActivity) { + if (it == true) { + list.submitItems(listOf( + MissingPermission( + context.getString(R.string.permission_files_search), + PermissionGroup.ExternalStorage, + secondaryActionLabel = context.getString(R.string.turn_off) + ) + ) + fileResults.value!!) + } else { + list.submitItems(fileResults.value) + } + } } } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/PermissionListRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/PermissionListRepresentation.kt index da0073ae..01295be8 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/PermissionListRepresentation.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/PermissionListRepresentation.kt @@ -1,30 +1,53 @@ package de.mm20.launcher2.ui.legacy.search -import android.app.Activity -import android.widget.TextView import androidx.appcompat.app.AppCompatActivity +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.ui.platform.ComposeView import androidx.transition.Scene import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.search.data.MissingPermission import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.ui.LegacyLauncherTheme import de.mm20.launcher2.ui.R +import de.mm20.launcher2.ui.component.MissingPermissionBanner import de.mm20.launcher2.ui.legacy.searchable.SearchableView -import de.mm20.launcher2.ui.legacy.view.InnerCardView -import de.mm20.launcher2.ui.legacy.view.LauncherIconView import org.koin.core.component.KoinComponent import org.koin.core.component.get class PermissionListRepresentation : Representation, KoinComponent { - override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene { + override fun getScene( + rootView: SearchableView, + searchable: Searchable, + previousRepresentation: Int? + ): Scene { val missingPermission = searchable as MissingPermission val context = rootView.context - val scene = Scene.getSceneForLayout(rootView, R.layout.view_permission_list, rootView.context) + val scene = + Scene.getSceneForLayout(rootView, R.layout.view_permission_list, rootView.context) scene.setEnterAction { val permissionsManager: PermissionsManager = get() - rootView.findViewById(R.id.permissionText).text = missingPermission.label - rootView.findViewById(R.id.permissionIcon).icon = missingPermission.getPlaceholderIcon(context) - rootView.findViewById(R.id.card).setOnClickListener { - permissionsManager.requestPermission(context as AppCompatActivity, missingPermission.permissionGroup) + rootView.findViewById(R.id.composeView).setContent { + LegacyLauncherTheme { + MissingPermissionBanner( + text = missingPermission.label, + onClick = { + permissionsManager.requestPermission( + context as AppCompatActivity, + missingPermission.permissionGroup + ) + }, + secondaryAction = { + val secondaryAction = missingPermission.secondaryAction + val secondaryActionLabel = missingPermission.secondaryActionLabel + if (secondaryAction != null && secondaryActionLabel != null) + TextButton(onClick = secondaryAction) { + Text(text = secondaryActionLabel, style = MaterialTheme.typography.labelLarge) + } + } + ) + } } } return scene diff --git a/ui/src/main/res/layout/view_permission_list.xml b/ui/src/main/res/layout/view_permission_list.xml index ccf662ff..6ed12f9c 100644 --- a/ui/src/main/res/layout/view_permission_list.xml +++ b/ui/src/main/res/layout/view_permission_list.xml @@ -1,37 +1,10 @@ - - - - - - - \ No newline at end of file + android:transitionName="root" /> \ No newline at end of file