Re-add permission banners to search results
This commit is contained in:
parent
1aa3eeaa1a
commit
a6c4b6431b
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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)
|
||||||
)
|
)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user