Fix permission banners in search results

This commit is contained in:
MM20 2022-01-26 21:51:35 +01:00
parent 7a48a148ff
commit b8304f1c1c
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
8 changed files with 270 additions and 117 deletions

View File

@ -477,4 +477,5 @@
<string name="no_account_microsoft">Sie haben noch kein Microsoft-Konto verbunden</string>
<string name="no_account_google">Sie haben noch kein Google-Konto verbunden</string>
<string name="connect_account">Konto verbinden</string>
<string name="turn_off">Deaktivieren</string>
</resources>

View File

@ -516,4 +516,6 @@
<string name="no_account_microsoft">You haven\'t connected a Microsoft account yet</string>
<string name="no_account_google">You haven\'t connected a Google account yet</string>
<string name="connect_account">Connect account</string>
<string name="turn_off">Turn off</string>
</resources>

View File

@ -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}"

View File

@ -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<List<CalendarEvent>?>
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<ViewGroup>(R.id.card)
card.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
val list = findViewById<SearchListView>(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<Boolean>()
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<SearchListView>(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)
}
}
}
}

View File

@ -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<List<Contact>?>
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<ViewGroup>(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<Boolean>()
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<SearchListView>(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)
}
}
}
}

View File

@ -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<List<File>?>
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<ViewGroup>(R.id.card)
card.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
val list = findViewById<SearchListView>(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<Boolean>()
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<SearchListView>(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)
}
}
}
}

View File

@ -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<TextView>(R.id.permissionText).text = missingPermission.label
rootView.findViewById<LauncherIconView>(R.id.permissionIcon).icon = missingPermission.getPlaceholderIcon(context)
rootView.findViewById<InnerCardView>(R.id.card).setOnClickListener {
permissionsManager.requestPermission(context as AppCompatActivity, missingPermission.permissionGroup)
rootView.findViewById<ComposeView>(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

View File

@ -1,37 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<de.mm20.launcher2.ui.legacy.view.InnerCardView 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:id="@+id/card"
<androidx.compose.ui.platform.ComposeView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/composeView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:foreground="?selectableItemBackground"
android:transitionName="root">
<TextView
android:id="@+id/permissionText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="12dp"
android:layout_marginStart="56dp"
android:textAppearance="?textAppearanceBodyMedium"
app:layout_constraintBottom_toTopOf="@+id/guideline4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/calendarColor"
android:layout_gravity="center_vertical"
tools:text="Information" />
<de.mm20.launcher2.ui.legacy.view.LauncherIconView
android:id="@+id/permissionIcon"
android:elevation="1dp"
android:layout_gravity="center_vertical"
android:layout_width="48dp"
android:layout_margin="8dp"
android:layout_height="48dp" />
</de.mm20.launcher2.ui.legacy.view.InnerCardView>
android:transitionName="root" />