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 package de.mm20.launcher2.ui.launcher.search
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
@ -10,6 +11,9 @@ import de.mm20.launcher2.calendar.CalendarRepository
import de.mm20.launcher2.contacts.ContactRepository import de.mm20.launcher2.contacts.ContactRepository
import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.files.FileRepository 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.WebsearchRepository
import de.mm20.launcher2.search.data.* import de.mm20.launcher2.search.data.*
import de.mm20.launcher2.unitconverter.UnitConverterRepository import de.mm20.launcher2.unitconverter.UnitConverterRepository
@ -17,12 +21,17 @@ import de.mm20.launcher2.websites.WebsiteRepository
import de.mm20.launcher2.wikipedia.WikipediaRepository import de.mm20.launcher2.wikipedia.WikipediaRepository
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collectLatest 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.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
class SearchVM : ViewModel(), KoinComponent { class SearchVM : ViewModel(), KoinComponent {
private val favoritesRepository: FavoritesRepository by inject() 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 calendarRepository: CalendarRepository by inject()
private val contactRepository: ContactRepository by inject() private val contactRepository: ContactRepository by inject()
@ -36,6 +45,7 @@ class SearchVM : ViewModel(), KoinComponent {
val isSearching = MutableLiveData(false) val isSearching = MutableLiveData(false)
val searchQuery = MutableLiveData("") val searchQuery = MutableLiveData("")
val isSearchEmpty = MutableLiveData(true)
val favorites by lazy { val favorites by lazy {
favoritesRepository.getFavorites().asLiveData() favoritesRepository.getFavorites().asLiveData()
@ -60,6 +70,7 @@ class SearchVM : ViewModel(), KoinComponent {
var searchJob: Job? = null var searchJob: Job? = null
fun search(query: String) { fun search(query: String) {
searchQuery.value = query searchQuery.value = query
isSearchEmpty.value = query.isEmpty()
try { try {
searchJob?.cancel() searchJob?.cancel()
} catch (e: CancellationException) { } 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 package de.mm20.launcher2.ui.launcher.search.calendar
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding 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.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier 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.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel 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.LauncherCard
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.launcher.search.SearchVM import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList 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() { fun ColumnScope.CalendarResults() {
val viewModel: SearchVM = viewModel() val viewModel: SearchVM = viewModel()
val calendarEvents by viewModel.calendarResults.observeAsState(emptyList()) 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( LauncherCard(
modifier = Modifier modifier = Modifier
.padding(bottom = 8.dp) .padding(bottom = 8.dp)
.fillMaxWidth() .fillMaxWidth()
) { ) {
SearchResultList( Column {
items = calendarEvents, AnimatedVisibility(!isSearchEmpty && missingPermission) {
modifier = Modifier MissingPermissionBanner(
.fillMaxWidth() text = stringResource(R.string.missing_permission_calendar_search),
.padding(12.dp) 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 package de.mm20.launcher2.ui.launcher.search.contacts
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding 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.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier 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.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel 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.LauncherCard
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.launcher.search.SearchVM import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList
@Composable @Composable
fun ColumnScope.ContactResults() { fun ColumnScope.ContactResults() {
val viewModel: SearchVM = viewModel() val viewModel: SearchVM = viewModel()
val context = LocalContext.current
val contacts by viewModel.contactResults.observeAsState(emptyList()) 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( LauncherCard(
modifier = Modifier modifier = Modifier
.padding(bottom = 8.dp) .padding(bottom = 8.dp)
.fillMaxWidth() .fillMaxWidth()
) { ) {
SearchResultList( Column {
items = contacts, AnimatedVisibility(!isSearchEmpty && missingPermission) {
modifier = Modifier MissingPermissionBanner(
.fillMaxWidth() text = stringResource(R.string.missing_permission_contact_search),
.padding(12.dp) 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 package de.mm20.launcher2.ui.launcher.search.files
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding 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.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier 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.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel 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.LauncherCard
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.launcher.search.SearchVM import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.search.common.list.SearchResultList 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() { fun ColumnScope.FileResults() {
val viewModel: SearchVM = viewModel() val viewModel: SearchVM = viewModel()
val files by viewModel.fileResults.observeAsState(emptyList()) 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( LauncherCard(
modifier = Modifier modifier = Modifier
.padding(bottom = 8.dp) .padding(bottom = 8.dp)
.fillMaxWidth() .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( SearchResultList(
items = files, items = files,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(12.dp) .padding(12.dp)
) )}
} }
} }
} }