Re-add permission banners to search results

This commit is contained in:
MM20 2022-02-19 20:16:25 +01:00
parent 1aa3eeaa1a
commit a6c4b6431b
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
4 changed files with 182 additions and 17 deletions

View File

@ -1,5 +1,6 @@
package de.mm20.launcher2.ui.launcher.search
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
@ -10,6 +11,9 @@ import de.mm20.launcher2.calendar.CalendarRepository
import de.mm20.launcher2.contacts.ContactRepository
import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.files.FileRepository
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.WebsearchRepository
import de.mm20.launcher2.search.data.*
import de.mm20.launcher2.unitconverter.UnitConverterRepository
@ -17,12 +21,17 @@ import de.mm20.launcher2.websites.WebsiteRepository
import de.mm20.launcher2.wikipedia.WikipediaRepository
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class SearchVM : ViewModel(), KoinComponent {
private val favoritesRepository: FavoritesRepository by inject()
private val permissionsManager: PermissionsManager by inject()
private val dataStore: LauncherDataStore by inject()
private val calendarRepository: CalendarRepository by inject()
private val contactRepository: ContactRepository by inject()
@ -36,6 +45,7 @@ class SearchVM : ViewModel(), KoinComponent {
val isSearching = MutableLiveData(false)
val searchQuery = MutableLiveData("")
val isSearchEmpty = MutableLiveData(true)
val favorites by lazy {
favoritesRepository.getFavorites().asLiveData()
@ -60,6 +70,7 @@ class SearchVM : ViewModel(), KoinComponent {
var searchJob: Job? = null
fun search(query: String) {
searchQuery.value = query
isSearchEmpty.value = query.isEmpty()
try {
searchJob?.cancel()
} catch (e: CancellationException) {
@ -118,4 +129,62 @@ class SearchVM : ViewModel(), KoinComponent {
}
}
val missingCalendarPermission = combine(
permissionsManager.hasPermission(PermissionGroup.Calendar),
dataStore.data.map { it.calendarSearch.enabled }.distinctUntilChanged()
) { perm, enabled -> !perm && enabled }
fun requestCalendarPermission(context: AppCompatActivity) {
permissionsManager.requestPermission(context, PermissionGroup.Calendar)
}
fun disableCalendarSearch() {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setCalendarSearch(it.calendarSearch.toBuilder().setEnabled(false))
.build()
}
}
}
val missingContactsPermission = combine(
permissionsManager.hasPermission(PermissionGroup.Contacts),
dataStore.data.map { it.contactsSearch.enabled }.distinctUntilChanged()
) { perm, enabled -> !perm && enabled }
fun requestContactsPermission(context: AppCompatActivity) {
permissionsManager.requestPermission(context, PermissionGroup.Contacts)
}
fun disableContactsSearch() {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setContactsSearch(it.contactsSearch.toBuilder().setEnabled(false))
.build()
}
}
}
val missingFilesPermission = combine(
permissionsManager.hasPermission(PermissionGroup.ExternalStorage),
dataStore.data.map { it.fileSearch.localFiles }.distinctUntilChanged()
) { perm, enabled -> !perm && enabled }
fun requestFilesPermission(context: AppCompatActivity) {
permissionsManager.requestPermission(context, PermissionGroup.ExternalStorage)
}
fun disableFilesSearch() {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setFileSearch(it.fileSearch.toBuilder().setLocalFiles(false))
.build()
}
}
}
}

View File

@ -1,16 +1,26 @@
package de.mm20.launcher2.ui.launcher.search.calendar
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList
@ -18,19 +28,42 @@ import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList
fun ColumnScope.CalendarResults() {
val viewModel: SearchVM = viewModel()
val calendarEvents by viewModel.calendarResults.observeAsState(emptyList())
val context = LocalContext.current
AnimatedVisibility(calendarEvents.isNotEmpty()) {
val isSearchEmpty by viewModel.isSearchEmpty.observeAsState(true)
val missingPermission by viewModel.missingCalendarPermission.collectAsState(false)
AnimatedVisibility(calendarEvents.isNotEmpty() || (!isSearchEmpty && missingPermission)) {
LauncherCard(
modifier = Modifier
.padding(bottom = 8.dp)
.fillMaxWidth()
) {
SearchResultList(
items = calendarEvents,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
)
Column {
AnimatedVisibility(!isSearchEmpty && missingPermission) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_calendar_search),
onClick = { viewModel.requestCalendarPermission(context as AppCompatActivity) },
modifier = Modifier.padding(16.dp),
secondaryAction = {
TextButton(onClick = {
viewModel.disableCalendarSearch()
}) {
Text(
stringResource(R.string.turn_off),
style = MaterialTheme.typography.labelLarge
)
}
}
)
}
SearchResultList(
items = calendarEvents,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
)
}
}
}
}
}

View File

@ -1,36 +1,68 @@
package de.mm20.launcher2.ui.launcher.search.contacts
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList
@Composable
fun ColumnScope.ContactResults() {
val viewModel: SearchVM = viewModel()
val context = LocalContext.current
val contacts by viewModel.contactResults.observeAsState(emptyList())
AnimatedVisibility(contacts.isNotEmpty()) {
val isSearchEmpty by viewModel.isSearchEmpty.observeAsState(true)
val missingPermission by viewModel.missingContactsPermission.collectAsState(false)
AnimatedVisibility(contacts.isNotEmpty() || (!isSearchEmpty && missingPermission)) {
LauncherCard(
modifier = Modifier
.padding(bottom = 8.dp)
.fillMaxWidth()
) {
SearchResultList(
items = contacts,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
)
Column {
AnimatedVisibility(!isSearchEmpty && missingPermission) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_contact_search),
onClick = { viewModel.requestContactsPermission(context as AppCompatActivity) },
modifier = Modifier.padding(16.dp),
secondaryAction = {
TextButton(onClick = {
viewModel.disableContactsSearch()
}) {
Text(
stringResource(R.string.turn_off),
style = MaterialTheme.typography.labelLarge
)
}
}
)
}
SearchResultList(
items = contacts,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
)
}
}
}
}

View File

@ -1,16 +1,26 @@
package de.mm20.launcher2.ui.launcher.search.files
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList
@ -18,19 +28,40 @@ import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList
fun ColumnScope.FileResults() {
val viewModel: SearchVM = viewModel()
val files by viewModel.fileResults.observeAsState(emptyList())
val context = LocalContext.current
AnimatedVisibility(files.isNotEmpty()) {
val isSearchEmpty by viewModel.isSearchEmpty.observeAsState(true)
val missingPermission by viewModel.missingFilesPermission.collectAsState(false)
AnimatedVisibility(files.isNotEmpty() || (!isSearchEmpty && missingPermission)) {
LauncherCard(
modifier = Modifier
.padding(bottom = 8.dp)
.fillMaxWidth()
) {
Column {
AnimatedVisibility(!isSearchEmpty && missingPermission) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_files_search),
onClick = { viewModel.requestFilesPermission(context as AppCompatActivity) },
modifier = Modifier.padding(16.dp),
secondaryAction = {
TextButton(onClick = {
viewModel.disableFilesSearch()
}) {
Text(
stringResource(R.string.turn_off),
style = MaterialTheme.typography.labelLarge
)
}
}
)
}
SearchResultList(
items = files,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
)
)}
}
}
}