Annual refactor

This commit is contained in:
MM20
2023-10-28 14:20:16 +02:00
parent 4f64652aae
commit 957358c79a
110 changed files with 1786 additions and 1663 deletions
@@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
import de.mm20.launcher2.data.customattrs.utils.withCustomLabels
import de.mm20.launcher2.searchable.SearchableRepository
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.Tag
@@ -31,8 +31,8 @@ import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import coil.compose.AsyncImage
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.search.Application
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.LauncherApp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlin.math.abs
@@ -107,23 +107,12 @@ fun rememberSplashScreenData(searchable: SavableSearchable?): SplashScreenData {
LaunchedEffect(searchable) {
withContext(Dispatchers.IO) {
if (searchable is LauncherApp) {
val activityInfo = if (isAtLeastApiLevel(31)) {
searchable.launcherActivityInfo.activityInfo
} else {
try {
context.packageManager.getActivityInfo(
searchable.launcherActivityInfo.componentName,
0
)
} catch (e: PackageManager.NameNotFoundException) {
null
}
} ?: return@withContext
if (searchable is Application) {
val activityInfo = searchable.getActivityInfo(context) ?: return@withContext
val themeRes = activityInfo.themeResource
val ctx = try {
context.createPackageContext(
searchable.`package`,
searchable.componentName.packageName,
Context.CONTEXT_IGNORE_SECURITY
)
} catch (e: PackageManager.NameNotFoundException) {
@@ -8,7 +8,7 @@ import androidx.compose.runtime.setValue
import androidx.core.app.ActivityOptionsCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.searchable.SearchableRepository
import de.mm20.launcher2.searchable.SavableSearchableRepository
import de.mm20.launcher2.globalactions.GlobalActionsService
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
@@ -35,7 +35,7 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
private val dataStore: LauncherDataStore by inject()
private val globalActionsService: GlobalActionsService by inject()
private val permissionsManager: PermissionsManager by inject()
private val searchableRepository: SearchableRepository by inject()
private val searchableRepository: SavableSearchableRepository by inject()
private var isSystemInDarkMode = MutableStateFlow(false)
@@ -1,10 +1,7 @@
package de.mm20.launcher2.ui.launcher.search
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -18,29 +15,22 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.ExpandMore
import androidx.compose.material.icons.rounded.Person
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material.icons.rounded.Tag
import androidx.compose.material.icons.rounded.Work
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.platform.LocalContext
@@ -61,7 +51,7 @@ import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
import de.mm20.launcher2.ui.launcher.search.favorites.SearchFavoritesVM
import de.mm20.launcher2.ui.launcher.search.unitconverter.UnitConverterItem
import de.mm20.launcher2.ui.launcher.search.website.WebsiteItem
import de.mm20.launcher2.ui.launcher.search.wikipedia.WikipediaItem
import de.mm20.launcher2.ui.launcher.search.wikipedia.ArticleItem
import de.mm20.launcher2.ui.launcher.sheets.HiddenItemsSheet
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
import de.mm20.launcher2.ui.locals.LocalCardStyle
@@ -99,7 +89,7 @@ fun SearchColumn(
val events by viewModel.calendarResults
val unitConverter by viewModel.unitConverterResults
val calculator by viewModel.calculatorResults
val wikipedia by viewModel.wikipediaResults
val wikipedia by viewModel.articleResults
val website by viewModel.websiteResults
val hiddenResults by viewModel.hiddenResults
@@ -307,7 +297,7 @@ fun SearchColumn(
)
for (wiki in wikipedia) {
SingleResult(highlight = bestMatch == wiki) {
WikipediaItem(wikipedia = wiki)
ArticleItem(article = wiki)
}
}
for (ws in website) {
@@ -5,23 +5,24 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.searchable.SearchableRepository
import de.mm20.launcher2.searchable.SavableSearchableRepository
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.Settings.SearchResultOrderingSettings.Ordering
import de.mm20.launcher2.search.AppProfile
import de.mm20.launcher2.search.Contact
import de.mm20.launcher2.search.File
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchService
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.AppShortcut
import de.mm20.launcher2.search.Application
import de.mm20.launcher2.search.Article
import de.mm20.launcher2.search.CalendarEvent
import de.mm20.launcher2.search.Website
import de.mm20.launcher2.search.data.Calculator
import de.mm20.launcher2.search.data.CalendarEvent
import de.mm20.launcher2.search.data.Contact
import de.mm20.launcher2.search.data.File
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.data.UnitConverter
import de.mm20.launcher2.search.data.Website
import de.mm20.launcher2.search.data.Wikipedia
import de.mm20.launcher2.searchactions.actions.SearchAction
import de.mm20.launcher2.services.favorites.FavoritesService
import kotlinx.coroutines.CancellationException
@@ -43,7 +44,7 @@ import org.koin.core.component.inject
class SearchVM : ViewModel(), KoinComponent {
private val favoritesService: FavoritesService by inject()
private val searchableRepository: SearchableRepository by inject()
private val searchableRepository: SavableSearchableRepository by inject()
private val permissionsManager: PermissionsManager by inject()
private val dataStore: LauncherDataStore by inject()
@@ -55,13 +56,13 @@ class SearchVM : ViewModel(), KoinComponent {
val searchQuery = mutableStateOf("")
val isSearchEmpty = mutableStateOf(true)
val appResults = mutableStateOf<List<LauncherApp>>(emptyList())
val workAppResults = mutableStateOf<List<LauncherApp>>(emptyList())
val appResults = mutableStateOf<List<Application>>(emptyList())
val workAppResults = mutableStateOf<List<Application>>(emptyList())
val appShortcutResults = mutableStateOf<List<AppShortcut>>(emptyList())
val fileResults = mutableStateOf<List<File>>(emptyList())
val contactResults = mutableStateOf<List<Contact>>(emptyList())
val calendarResults = mutableStateOf<List<CalendarEvent>>(emptyList())
val wikipediaResults = mutableStateOf<List<Wikipedia>>(emptyList())
val articleResults = mutableStateOf<List<Article>>(emptyList())
val websiteResults = mutableStateOf<List<Website>>(emptyList())
val calculatorResults = mutableStateOf<List<Calculator>>(emptyList())
val unitConverterResults = mutableStateOf<List<UnitConverter>>(emptyList())
@@ -185,15 +186,15 @@ class SearchVM : ViewModel(), KoinComponent {
hiddenItemKeys.collectLatest { hiddenKeys ->
val hidden = mutableListOf<SavableSearchable>()
val apps = mutableListOf<LauncherApp>()
val workApps = mutableListOf<LauncherApp>()
val apps = mutableListOf<Application>()
val workApps = mutableListOf<Application>()
val shortcuts = mutableListOf<AppShortcut>()
val files = mutableListOf<File>()
val contacts = mutableListOf<Contact>()
val events = mutableListOf<CalendarEvent>()
val unitConv = mutableListOf<UnitConverter>()
val calc = mutableListOf<Calculator>()
val wikipedia = mutableListOf<Wikipedia>()
val articles = mutableListOf<Article>()
val website = mutableListOf<Website>()
val actions = mutableListOf<SearchAction>()
for (r in resultsList) {
@@ -202,8 +203,8 @@ class SearchVM : ViewModel(), KoinComponent {
hidden.add(r)
}
r is LauncherApp && !r.isMainProfile -> workApps.add(r)
r is LauncherApp -> apps.add(r)
r is Application && r.profile == AppProfile.Work -> workApps.add(r)
r is Application -> apps.add(r)
r is AppShortcut -> shortcuts.add(r)
r is File -> files.add(r)
r is Contact -> contacts.add(r)
@@ -211,7 +212,7 @@ class SearchVM : ViewModel(), KoinComponent {
r is UnitConverter -> unitConv.add(r)
r is Calculator -> calc.add(r)
r is Website -> website.add(r)
r is Wikipedia -> wikipedia.add(r)
r is Article -> articles.add(r)
r is SearchAction -> actions.add(r)
}
}
@@ -225,7 +226,7 @@ class SearchVM : ViewModel(), KoinComponent {
calc,
events,
contacts,
wikipedia,
articles,
website,
files,
actions
@@ -238,7 +239,7 @@ class SearchVM : ViewModel(), KoinComponent {
fileResults.value = files
contactResults.value = contacts
calendarResults.value = events
wikipediaResults.value = wikipedia
articleResults.value = articles
websiteResults.value = website
calculatorResults.value = calc
unitConverterResults.value = unitConv
@@ -66,7 +66,7 @@ import androidx.lifecycle.lifecycleScope
import coil.compose.AsyncImage
import com.google.accompanist.flowlayout.FlowRow
import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.Application
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.DefaultToolbarAction
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
@@ -82,12 +82,11 @@ import de.mm20.launcher2.ui.locals.LocalGridSettings
import de.mm20.launcher2.ui.locals.LocalSnackbarHostState
import de.mm20.launcher2.ui.modifier.scale
import kotlinx.coroutines.launch
import kotlin.math.pow
@Composable
fun AppItem(
modifier: Modifier = Modifier,
app: LauncherApp,
app: Application,
onBack: () -> Unit
) {
val viewModel: SearchableItemVM = listItemViewModel(key = "search-${app.key}")
@@ -127,7 +126,7 @@ fun AppItem(
}
app.version?.let {
app.versionName?.let {
Text(
text = stringResource(R.string.app_info_version, it),
style = MaterialTheme.typography.bodySmall,
@@ -137,7 +136,7 @@ fun AppItem(
)
}
Text(
text = app.`package`,
text = app.componentName.packageName,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(top = 1.dp),
maxLines = 1,
@@ -210,20 +209,21 @@ fun AppItem(
for (shortcut in shortcuts) {
val title =
shortcut.launcherShortcut.shortLabel
?: shortcut.launcherShortcut.longLabel
?: continue
shortcut.labelOverride ?: shortcut.label
val isPinned by remember(shortcut) { viewModel.isShortcutPinned(shortcut) }.collectAsState(
false
)
val icon =
val iconSizePx = InputChipDefaults.AvatarSize.toPixels()
val icon by
remember {
viewModel.getShortcutIcon(
context,
shortcut.launcherShortcut
shortcut,
iconSizePx.toInt()
)
}
}.collectAsState(null)
InputChip(
modifier = Modifier.width(IntrinsicSize.Max),
@@ -233,19 +233,17 @@ fun AppItem(
},
label = {
Text(
title.toString(),
title,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
},
avatar = {
AsyncImage(
model = icon,
contentDescription = null,
modifier = Modifier
.clip(CircleShape)
.size(InputChipDefaults.AvatarSize),
ShapedLauncherIcon(
size = InputChipDefaults.AvatarSize,
icon = { icon },
shape = CircleShape,
)
},
trailingIcon = if (LocalFavoritesEnabled.current) {
@@ -311,7 +309,7 @@ fun AppItem(
label = stringResource(R.string.menu_app_info),
icon = Icons.Rounded.Info
) {
app.openAppInfo(context)
app.openAppDetails(context)
})
toolbarActions.add(
@@ -429,7 +427,7 @@ fun AppItem(
@Composable
fun AppItemGridPopup(
app: LauncherApp,
app: Application,
show: MutableTransitionState<Boolean>,
animationProgress: Float,
origin: Rect,
@@ -454,7 +452,7 @@ fun AppItemGridPopup(
transformOrigin = TransformOrigin(1f, 0f)
)
.offset(
x = lerp(16.dp, 0.dp, animationProgress),
x = lerp(16.dp, 0.dp, animationProgress),
y = lerp(-16.dp, 0.dp, animationProgress)
),
app = app,
@@ -41,13 +41,14 @@ import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.roundToIntRect
import androidx.lifecycle.lifecycleScope
import de.mm20.launcher2.search.data.CalendarEvent
import de.mm20.launcher2.search.CalendarEvent
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.animation.animateTextStyleAsState
import de.mm20.launcher2.ui.component.DefaultToolbarAction
@@ -83,12 +84,13 @@ fun CalendarItem(
val snackbarHostState = LocalSnackbarHostState.current
val darkMode = LocalDarkTheme.current
val secondaryColor = MaterialTheme.colorScheme.secondary
Row(
modifier = modifier
.drawBehind {
val color = TonalPalette
.fromInt(calendar.color)
.fromInt(calendar.color ?: secondaryColor.toArgb())
.tone(
if (darkMode) 80 else 40
)
@@ -151,7 +153,7 @@ fun CalendarItem(
style = MaterialTheme.typography.bodySmall
)
}
if (calendar.description.isNotBlank()) {
if (calendar.description != null) {
Row(
Modifier
.fillMaxWidth(),
@@ -163,7 +165,7 @@ fun CalendarItem(
contentDescription = null
)
Text(
text = calendar.description,
text = calendar.description!!,
style = MaterialTheme.typography.bodySmall
)
}
@@ -185,7 +187,7 @@ fun CalendarItem(
)
}
}
if (calendar.location.isNotBlank()) {
if (calendar.location != null) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
@@ -200,7 +202,7 @@ fun CalendarItem(
contentDescription = null
)
Text(
text = calendar.location,
text = calendar.location!!,
style = MaterialTheme.typography.bodySmall
)
}
@@ -320,8 +322,7 @@ fun CalendarItemGridPopup(
) {
CalendarItem(
modifier = Modifier
.fillMaxWidth()
.background(Color(calendar.color).copy(alpha = 1f - animationProgress)),
.fillMaxWidth(),
calendar = calendar,
showDetails = true,
onBack = onDismiss
@@ -4,27 +4,22 @@ import android.content.Context
import android.content.pm.LauncherApps
import android.content.pm.ShortcutInfo
import android.graphics.drawable.Drawable
import android.service.notification.StatusBarNotification
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.ui.geometry.Rect
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.getSystemService
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.appshortcuts.AppShortcutRepository
import de.mm20.launcher2.badges.BadgeService
import de.mm20.launcher2.files.FileRepository
import de.mm20.launcher2.icons.IconService
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.notifications.Notification
import de.mm20.launcher2.notifications.NotificationRepository
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.search.File
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.data.File
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.data.LauncherShortcut
import de.mm20.launcher2.search.AppShortcut
import de.mm20.launcher2.search.Application
import de.mm20.launcher2.services.favorites.FavoritesService
import de.mm20.launcher2.services.tags.TagsService
import de.mm20.launcher2.ui.launcher.search.ListItemViewModel
@@ -33,9 +28,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@@ -46,7 +43,6 @@ class SearchableItemVM : ListItemViewModel(), KoinComponent {
private val tagsService: TagsService by inject()
private val notificationRepository: NotificationRepository by inject()
private val appShortcutRepository: AppShortcutRepository by inject()
private val fileRepository: FileRepository by inject()
private val permissionsManager: PermissionsManager by inject()
private val searchable = MutableStateFlow<SavableSearchable?>(null)
@@ -93,13 +89,19 @@ class SearchableItemVM : ListItemViewModel(), KoinComponent {
}
val notifications = searchable.flatMapLatest { searchable ->
if (searchable !is LauncherApp) emptyFlow()
else notificationRepository.notifications.map { it.filter { it.packageName == searchable.`package` && !it.isGroupSummary } }
if (searchable !is Application) emptyFlow()
else notificationRepository.notifications.map { it.filter { it.packageName == searchable.componentName.packageName && !it.isGroupSummary } }
}
val shortcuts = searchable.map {
if (it !is LauncherApp) emptyList()
else appShortcutRepository.getShortcutsForActivity(it.launcherActivityInfo, 5)
if (it !is Application) emptyList()
else appShortcutRepository
.findMany(
componentName = it.componentName,
user = it.user,
manifest = true,
dynamic = true,
).first()
}
open fun launch(context: Context, bounds: Rect? = null): Boolean {
@@ -120,7 +122,7 @@ class SearchableItemVM : ListItemViewModel(), KoinComponent {
if (searchable.launch(context, bundle)) {
favoritesService.reportLaunch(searchable)
return true
} else if (searchable is LauncherApp || searchable is AppShortcut) {
} else if (searchable is Application || searchable is AppShortcut) {
favoritesService.reset(searchable)
}
return false
@@ -130,9 +132,8 @@ class SearchableItemVM : ListItemViewModel(), KoinComponent {
notificationRepository.cancelNotification(notification)
}
fun getShortcutIcon(context: Context, shortcut: ShortcutInfo): Drawable? {
val launcherApps = context.getSystemService<LauncherApps>() ?: return null
return launcherApps.getShortcutIconDrawable(shortcut, 0)
fun getShortcutIcon(context: Context, shortcut: AppShortcut, size: Int): Flow<LauncherIcon?> {
return iconService.getIcon(shortcut, size)
}
fun isShortcutPinned(shortcut: AppShortcut): Flow<Boolean> {
@@ -151,10 +152,18 @@ class SearchableItemVM : ListItemViewModel(), KoinComponent {
shortcut.launch(context, null)
}
fun delete() {
fun delete(context: Context) {
val searchable = searchable.value ?: return
if (searchable is File) fileRepository.deleteFile(searchable)
if (searchable is LauncherShortcut) appShortcutRepository.removePinnedShortcut(searchable)
if (searchable is File) {
viewModelScope.launch {
searchable.delete(context.applicationContext)
}
}
if (searchable is AppShortcut) {
viewModelScope.launch {
searchable.delete(context.applicationContext)
}
}
favoritesService.reset(searchable)
}
@@ -1,7 +1,5 @@
package de.mm20.launcher2.ui.launcher.search.common.grid
import android.content.ComponentName
import android.util.Log
import androidx.activity.compose.BackHandler
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.MutableTransitionState
@@ -42,18 +40,17 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.mm20.launcher2.search.Contact
import de.mm20.launcher2.search.File
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.data.CalendarEvent
import de.mm20.launcher2.search.data.Contact
import de.mm20.launcher2.search.data.File
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.data.Website
import de.mm20.launcher2.search.data.Wikipedia
import de.mm20.launcher2.search.AppShortcut
import de.mm20.launcher2.search.Application
import de.mm20.launcher2.search.Article
import de.mm20.launcher2.search.CalendarEvent
import de.mm20.launcher2.search.Website
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.component.LocalIconShape
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
@@ -66,13 +63,12 @@ import de.mm20.launcher2.ui.launcher.search.files.FileItemGridPopup
import de.mm20.launcher2.ui.launcher.search.listItemViewModel
import de.mm20.launcher2.ui.launcher.search.shortcut.ShortcutItemGridPopup
import de.mm20.launcher2.ui.launcher.search.website.WebsiteItemGridPopup
import de.mm20.launcher2.ui.launcher.search.wikipedia.WikipediaItemGridPopup
import de.mm20.launcher2.ui.launcher.search.wikipedia.ArticleItemGridPopup
import de.mm20.launcher2.ui.launcher.transitions.EnterHomeTransitionParams
import de.mm20.launcher2.ui.launcher.transitions.HandleEnterHomeTransition
import de.mm20.launcher2.ui.locals.LocalGridSettings
import de.mm20.launcher2.ui.locals.LocalWindowSize
import de.mm20.launcher2.ui.overlays.Overlay
import kotlin.math.min
import kotlin.math.pow
@@ -121,9 +117,9 @@ fun GridItem(
val windowSize = LocalWindowSize.current
if (item is LauncherApp) {
if (item is Application) {
HandleEnterHomeTransition {
val cn = ComponentName(item.`package`, item.activity)
val cn = item.componentName
if (
it.componentName == cn &&
bounds.right > 0f && bounds.left < windowSize.width &&
@@ -240,7 +236,7 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
)
) {
when (searchable) {
is LauncherApp -> {
is Application -> {
AppItemGridPopup(
app = searchable,
show = show,
@@ -264,9 +260,9 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
)
}
is Wikipedia -> {
WikipediaItemGridPopup(
wikipedia = searchable,
is Article -> {
ArticleItemGridPopup(
article = searchable,
show = show,
animationProgress = animationProgress.value,
origin = origin,
@@ -9,9 +9,11 @@ import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.search.AppShortcut
import de.mm20.launcher2.search.CalendarEvent
import de.mm20.launcher2.search.Contact
import de.mm20.launcher2.search.File
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.*
import de.mm20.launcher2.ui.component.InnerCard
import de.mm20.launcher2.ui.ktx.toPixels
import de.mm20.launcher2.ui.launcher.search.calendar.CalendarItem
@@ -17,16 +17,20 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.Message
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.Call
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.Email
import androidx.compose.material.icons.rounded.Home
import androidx.compose.material.icons.rounded.MoreHoriz
import androidx.compose.material.icons.rounded.OpenInNew
import androidx.compose.material.icons.rounded.Place
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material.icons.rounded.StarOutline
import androidx.compose.material.icons.rounded.Visibility
import androidx.compose.material.icons.rounded.VisibilityOff
import androidx.compose.material.icons.rounded.Whatsapp
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarDuration
@@ -36,6 +40,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
@@ -49,7 +54,8 @@ import androidx.compose.ui.unit.roundToIntRect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.search.data.Contact
import de.mm20.launcher2.search.Contact
import de.mm20.launcher2.search.ContactInfoType
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.animation.animateTextStyleAsState
import de.mm20.launcher2.ui.component.Chip
@@ -144,164 +150,38 @@ fun ContactItem(
}
AnimatedVisibility(showDetails) {
val groups = remember {
contact.contactInfos.groupBy { it.type }
}
Column {
if (contact.phones.isNotEmpty()) {
for ((type, items) in groups) {
Row(
modifier = Modifier.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.Call, contentDescription = null)
Icon(when(type) {
ContactInfoType.Phone -> Icons.Rounded.Call
ContactInfoType.Message -> Icons.AutoMirrored.Rounded.Message
ContactInfoType.Email -> Icons.Rounded.Email
ContactInfoType.Postal -> Icons.Rounded.Home
ContactInfoType.Telegram -> Icons.Rounded.Telegram
ContactInfoType.Whatsapp -> Icons.Rounded.Whatsapp
ContactInfoType.Signal -> Icons.Rounded.Signal
ContactInfoType.Other -> Icons.Rounded.MoreHoriz
}, contentDescription = null)
LazyRow(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
items(contact.phones.toList()) {
items(items.toList()) {
Chip(
modifier = Modifier.padding(end = 16.dp),
text = it.label,
onClick = {
context.tryStartActivity(
Intent(Intent.ACTION_VIEW).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(Uri.parse(it.data))
)
}
)
}
}
}
}
if (contact.emails.isNotEmpty()) {
Row(
modifier = Modifier.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.Email, contentDescription = null)
LazyRow(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
items(contact.emails.toList()) {
Chip(
modifier = Modifier.padding(end = 16.dp),
text = it.label,
onClick = {
context.tryStartActivity(
Intent(Intent.ACTION_VIEW).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(Uri.parse(it.data))
)
}
)
}
}
}
}
if (contact.signal.isNotEmpty()) {
Row(
modifier = Modifier.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.Signal, contentDescription = null)
LazyRow(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
items(contact.signal.toList()) {
Chip(
modifier = Modifier.padding(end = 16.dp),
text = it.label,
onClick = {
context.tryStartActivity(
Intent(Intent.ACTION_VIEW).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(Uri.parse(it.data))
)
}
)
}
}
}
}
if (contact.telegram.isNotEmpty()) {
Row(
modifier = Modifier.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.Telegram, contentDescription = null)
LazyRow(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
items(contact.telegram.toList()) {
Chip(
modifier = Modifier.padding(end = 16.dp),
text = it.label,
onClick = {
context.tryStartActivity(
Intent(Intent.ACTION_VIEW).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(Uri.parse(it.data))
)
}
)
}
}
}
}
if (contact.whatsapp.isNotEmpty()) {
Row(
modifier = Modifier.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.WhatsApp, contentDescription = null)
LazyRow(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
items(contact.whatsapp.toList()) {
Chip(
modifier = Modifier.padding(end = 16.dp),
text = it.label,
onClick = {
context.tryStartActivity(
Intent(Intent.ACTION_VIEW).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(Uri.parse(it.data))
)
}
)
}
}
}
}
if (contact.postals.isNotEmpty()) {
Row(
modifier = Modifier.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.Place, contentDescription = null)
LazyRow(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
items(contact.postals.toList()) {
Chip(
modifier = Modifier.padding(end = 16.dp),
text = it.label,
onClick = {
context.tryStartActivity(
Intent(Intent.ACTION_VIEW).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(Uri.parse(it.data))
)
context.tryStartActivity(it.intent)
}
)
}
@@ -47,7 +47,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.roundToIntRect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import de.mm20.launcher2.search.data.File
import de.mm20.launcher2.search.File
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.animation.animateTextStyleAsState
import de.mm20.launcher2.ui.component.DefaultToolbarAction
@@ -236,7 +236,7 @@ fun FileItem(
onDismissRequest = { showConfirmDialog = false },
confirmButton = {
TextButton(onClick = {
viewModel.delete()
viewModel.delete(context)
showConfirmDialog = false
}) {
Text(stringResource(android.R.string.ok))
@@ -53,10 +53,7 @@ import androidx.compose.ui.unit.roundToIntRect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.data.LauncherShortcut
import de.mm20.launcher2.search.data.LegacyShortcut
import de.mm20.launcher2.search.data.UnavailableShortcut
import de.mm20.launcher2.search.AppShortcut
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.animation.animateTextStyleAsState
import de.mm20.launcher2.ui.component.DefaultToolbarAction
@@ -101,7 +98,7 @@ fun AppShortcutItem(
Column(
modifier = modifier
) {
AnimatedVisibility(showDetails && shortcut is UnavailableShortcut) {
AnimatedVisibility(showDetails && shortcut.isUnavailable) {
MissingPermissionBanner(
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp),
text = stringResource(R.string.shortcut_unavailable_description, stringResource(R.string.app_name)),
@@ -213,7 +210,7 @@ fun AppShortcutItem(
action = { sheetManager.showCustomizeSearchableModal(shortcut) }
))
if (shortcut is LauncherShortcut && shortcut.launcherShortcut.isPinned) {
if (shortcut.canDelete) {
toolbarActions.add(DefaultToolbarAction(
label = stringResource(R.string.menu_delete),
icon = Icons.Rounded.Delete,
@@ -276,7 +273,7 @@ fun AppShortcutItem(
text = { Text(stringResource(R.string.alert_delete_shortcut, shortcut.label)) },
confirmButton = {
TextButton(onClick = {
viewModel.delete()
viewModel.delete(context)
requestDelete = false
}) {
Text(stringResource(android.R.string.ok))
@@ -330,9 +327,3 @@ fun ShortcutItemGridPopup(
}
}
val AppShortcut.packageName: String?
get() = when (this) {
is LegacyShortcut -> intent.`package`
is LauncherShortcut -> launcherShortcut.`package`
else -> null
}
@@ -32,7 +32,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.roundToIntRect
import coil.compose.AsyncImage
import de.mm20.launcher2.search.data.Website
import de.mm20.launcher2.search.Website
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.DefaultToolbarAction
import de.mm20.launcher2.ui.component.Toolbar
@@ -64,13 +64,13 @@ fun WebsiteItem(
viewModel.launch(context)
}
) {
if (website.image.isNotBlank()) {
if (website.imageUrl != null) {
AsyncImage(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(16f / 9f)
.background(MaterialTheme.colorScheme.secondaryContainer),
model = website.image,
model = website.imageUrl,
contentScale = ContentScale.Crop,
contentDescription = null
)
@@ -93,7 +93,7 @@ fun WebsiteItem(
}
Text(
modifier = Modifier.padding(vertical = 8.dp),
text = website.description,
text = website.description ?: "",
style = MaterialTheme.typography.bodySmall
)
}
@@ -32,7 +32,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.roundToIntRect
import coil.compose.AsyncImage
import de.mm20.launcher2.search.data.Wikipedia
import de.mm20.launcher2.search.Article
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.DefaultToolbarAction
import de.mm20.launcher2.ui.component.Toolbar
@@ -46,18 +46,18 @@ import de.mm20.launcher2.ui.locals.LocalGridSettings
import de.mm20.launcher2.ui.utils.htmlToAnnotatedString
@Composable
fun WikipediaItem(
fun ArticleItem(
modifier: Modifier = Modifier,
wikipedia: Wikipedia,
article: Article,
onBack: (() -> Unit)? = null
) {
val context = LocalContext.current
val viewModel: SearchableItemVM = listItemViewModel(key = "search-${wikipedia.key}")
val viewModel: SearchableItemVM = listItemViewModel(key = "search-${article.key}")
val iconSize = LocalGridSettings.current.iconSize.dp.toPixels()
LaunchedEffect(wikipedia, iconSize) {
viewModel.init(wikipedia, iconSize.toInt())
LaunchedEffect(article, iconSize) {
viewModel.init(article, iconSize.toInt())
}
Column(
@@ -65,13 +65,13 @@ fun WikipediaItem(
viewModel.launch(context)
}
) {
if (!wikipedia.image.isNullOrEmpty()) {
if (!article.imageUrl.isNullOrEmpty()) {
AsyncImage(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(16f / 9f)
.background(MaterialTheme.colorScheme.secondaryContainer),
model = wikipedia.image,
model = article.imageUrl,
contentScale = ContentScale.Crop,
contentDescription = null
)
@@ -80,7 +80,7 @@ fun WikipediaItem(
modifier = Modifier.padding(16.dp),
) {
Text(
text = wikipedia.label,
text = article.label,
style = MaterialTheme.typography.titleLarge
)
val tags by viewModel.tags.collectAsState(emptyList())
@@ -100,7 +100,7 @@ fun WikipediaItem(
)
Text(
modifier = Modifier.padding(vertical = 8.dp),
text = htmlToAnnotatedString(wikipedia.text),
text = htmlToAnnotatedString(article.text),
style = MaterialTheme.typography.bodySmall
)
}
@@ -134,7 +134,7 @@ fun WikipediaItem(
label = stringResource(R.string.menu_share),
icon = Icons.Rounded.Share,
action = {
wikipedia.share(context)
article.share(context)
}
)
)
@@ -143,7 +143,7 @@ fun WikipediaItem(
toolbarActions.add(DefaultToolbarAction(
label = stringResource(R.string.menu_customize),
icon = Icons.Rounded.Edit,
action = { sheetManager.showCustomizeSearchableModal(wikipedia) }
action = { sheetManager.showCustomizeSearchableModal(article) }
))
Toolbar(
@@ -160,8 +160,8 @@ fun WikipediaItem(
}
@Composable
fun WikipediaItemGridPopup(
wikipedia: Wikipedia,
fun ArticleItemGridPopup(
article: Article,
show: MutableTransitionState<Boolean>,
animationProgress: Float,
origin: Rect,
@@ -178,10 +178,10 @@ fun WikipediaItemGridPopup(
shrinkTowards = Alignment.Center,
) { origin.roundToIntRect().size },
) {
WikipediaItem(
ArticleItem(
modifier = Modifier
.fillMaxWidth(),
wikipedia = wikipedia,
article = article,
onBack = onDismiss
)
}
@@ -74,7 +74,7 @@ import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.search.data.UserCalendar
import de.mm20.launcher2.calendar.UserCalendar
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.LargeMessage
@@ -3,6 +3,7 @@ package de.mm20.launcher2.ui.launcher.sheets
import android.app.Activity
import android.content.Context
import android.content.pm.LauncherApps
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
@@ -64,6 +65,7 @@ import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
@@ -73,6 +75,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.SavableSearchable
@@ -647,7 +650,6 @@ fun ShortcutPicker(viewModel: EditFavoritesSheetVM, paddingValues: PaddingValues
}
val iconSize = 48.dp.toPixels().roundToInt()
val activity = LocalLifecycleOwner.current as AppCompatActivity
LazyColumn(
contentPadding = paddingValues
@@ -664,31 +666,28 @@ fun ShortcutPicker(viewModel: EditFavoritesSheetVM, paddingValues: PaddingValues
}
}
items(shortcutActivities) {
val icon by remember(it.key) { viewModel.getIcon(it, iconSize) }.collectAsState(null)
val badge by remember(it.key) { viewModel.getBadge(it) }.collectAsState(null)
val icon by remember(it) { it.getIcon(context) }.collectAsState(null)
OutlinedCard(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
onClick = {
val launcherApps =
context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val sender =
launcherApps.getShortcutConfigActivityIntent(it.launcherActivityInfo)
?: return@OutlinedCard
activityLauncher.launch(IntentSenderRequest.Builder(sender).build(), null)
val intent = it.getIntent(context) ?: return@OutlinedCard run {
Log.e("MM20", "Couldn't get intent for shortcut")
}
activityLauncher.launch(IntentSenderRequest.Builder(intent).build(), null)
}) {
Row(
modifier = Modifier.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
ShapedLauncherIcon(
size = 48.dp,
icon = { icon },
badge = { badge },
AsyncImage(
model = icon,
contentDescription = null,
modifier = Modifier.size(48.dp)
)
Text(
text = it.labelOverride ?: it.label,
text = it.label,
modifier = Modifier.padding(start = 16.dp),
style = MaterialTheme.typography.titleSmall
)
@@ -19,7 +19,7 @@ import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.appshortcuts.AppShortcut
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.Tag
import de.mm20.launcher2.services.favorites.FavoritesService
@@ -210,7 +210,7 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
fun createShortcut(context: Context, data: Intent?) {
data ?: return cancelPickShortcut()
val shortcut = AppShortcut.fromPinRequestIntent(context, data)
val shortcut = AppShortcut(context, data)
if (shortcut == null) {
cancelPickShortcut()
@@ -9,11 +9,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.calendar.CalendarRepository
import de.mm20.launcher2.searchable.SearchableRepository
import de.mm20.launcher2.searchable.SavableSearchableRepository
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.search.data.CalendarEvent
import de.mm20.launcher2.search.CalendarEvent
import de.mm20.launcher2.services.favorites.FavoritesService
import de.mm20.launcher2.widgets.CalendarWidget
import de.mm20.launcher2.widgets.CalendarWidgetConfig
@@ -34,14 +34,14 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
private val calendarRepository: CalendarRepository by inject()
private val favoritesService: FavoritesService by inject()
private val searchableRepository: SearchableRepository by inject()
private val searchableRepository: SavableSearchableRepository by inject()
private val widgetConfig = MutableStateFlow(CalendarWidgetConfig())
val calendarEvents = mutableStateOf<List<CalendarEvent>>(emptyList())
val pinnedCalendarEvents =
favoritesService.getFavorites(
includeTypes = listOf(CalendarEvent.Domain),
includeTypes = listOf("calendar"),
automaticallySorted = true,
manuallySorted = true,
).stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
@@ -164,12 +164,14 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
suspend fun onActive() {
selectDate(LocalDate.now())
widgetConfig.collectLatest { config ->
calendarRepository.getUpcomingEvents(
calendarRepository.findMany(
from = System.currentTimeMillis(),
to = System.currentTimeMillis() + 14 * 24 * 60 * 60 * 1000L,
excludeAllDayEvents = !config.allDayEvents,
excludeCalendars = config.excludedCalendarIds,
).collectLatest { events ->
searchableRepository.getKeys(
includeTypes = listOf(CalendarEvent.Domain),
includeTypes = listOf("calendar"),
hidden = true,
limit = 9999,
).collectLatest { hidden ->
@@ -11,7 +11,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.searchable.SearchableRepository
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.Settings.ClockWidgetSettings.ClockWidgetLayout
import de.mm20.launcher2.services.favorites.FavoritesService
@@ -3,13 +3,13 @@ package de.mm20.launcher2.ui.settings.debug
import androidx.lifecycle.ViewModel
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
import de.mm20.launcher2.icons.IconService
import de.mm20.launcher2.searchable.SearchableRepository
import de.mm20.launcher2.searchable.SavableSearchableRepository
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class DebugSettingsScreenVM: ViewModel(), KoinComponent {
private val searchableRepository: SearchableRepository by inject()
private val searchableRepository: SavableSearchableRepository by inject()
private val customAttributesRepository: CustomAttributesRepository by inject()
private val iconService: IconService by inject()
suspend fun cleanUpDatabase(): Int {
@@ -3,7 +3,7 @@ package de.mm20.launcher2.ui.settings.gestures
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.searchable.SearchableRepository
import de.mm20.launcher2.searchable.SavableSearchableRepository
import de.mm20.launcher2.icons.IconService
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.permissions.PermissionGroup
@@ -24,7 +24,7 @@ import org.koin.core.component.inject
class GestureSettingsScreenVM : ViewModel(), KoinComponent {
private val dataStore: LauncherDataStore by inject()
private val permissionsManager: PermissionsManager by inject()
private val searchableRepository: SearchableRepository by inject()
private val searchableRepository: SavableSearchableRepository by inject()
private val iconService: IconService by inject()
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Accessibility)
@@ -1,20 +1,17 @@
package de.mm20.launcher2.ui.settings.hiddenitems
import android.content.ComponentName
import android.content.Context
import android.content.pm.LauncherApps
import android.os.Bundle
import androidx.core.content.getSystemService
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.applications.AppRepository
import de.mm20.launcher2.searchable.SearchableRepository
import de.mm20.launcher2.searchable.SavableSearchableRepository
import de.mm20.launcher2.icons.IconService
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.Application
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -30,16 +27,16 @@ import org.koin.core.component.inject
class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent {
private val appRepository: AppRepository by inject()
private val searchableRepository: SearchableRepository by inject()
private val searchableRepository: SavableSearchableRepository by inject()
private val iconService: IconService by inject()
private val dataStore: LauncherDataStore by inject()
val allApps = appRepository.getAllInstalledApps().map {
val allApps = appRepository.findMany().map {
withContext(Dispatchers.Default) { it.sorted() }
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
val hiddenItems: StateFlow<List<SavableSearchable>> = flow {
val hidden =
searchableRepository.get(hidden = true).first().filter { it !is LauncherApp }.sorted()
searchableRepository.get(hidden = true).first().filter { it !is Application }.sorted()
emit(hidden)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
@@ -67,15 +64,8 @@ class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent {
searchable.launch(context, bundle)
}
fun openAppInfo(context: Context, app: LauncherApp) {
val launcherApps = context.getSystemService<LauncherApps>()!!
launcherApps.startAppDetailsActivity(
ComponentName(app.`package`, app.activity),
app.getUser(),
null,
null
)
fun openAppInfo(context: Context, app: Application) {
app.openAppDetails(context)
}
val hiddenItemsButton = dataStore.data.map { it.searchBar.hiddenItemsButton }
@@ -12,7 +12,7 @@ import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.Application
import de.mm20.launcher2.services.favorites.FavoritesService
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -224,7 +224,7 @@ class IconsSettingsScreenVM(
fun getPreviewIcons(size: Int): Flow<List<LauncherIcon?>> {
return columnCount.flatMapLatest { cols ->
favoritesService.getFavorites(
includeTypes = listOf(LauncherApp.Domain),
includeTypes = listOf("app"),
limit = cols,
manuallySorted = true,
automaticallySorted = true,
@@ -12,6 +12,7 @@ import de.mm20.launcher2.music.MusicService
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.AppProfile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -49,8 +50,8 @@ class MediaIntegrationSettingsScreenVM : ViewModel(), KoinComponent {
loading.value = true
viewModelScope.launch(Dispatchers.Default) {
val musicApps = musicService.getInstalledPlayerPackages()
val allApps = appRepository.getAllInstalledApps().first().filter { it.isMainProfile }
.distinctBy { it.`package` }
val allApps = appRepository.findMany().first().filter { it.profile == AppProfile.Personal }
.distinctBy { it.componentName.packageName }
val settings = dataStore.data.map { it.musicWidget }.first()
val allowList = settings.allowListList
val denyList = settings.denyListList
@@ -58,10 +59,10 @@ class MediaIntegrationSettingsScreenVM : ViewModel(), KoinComponent {
appList.value = allApps.map {
AppListItem(
label = it.label,
packageName = it.`package`,
isMusicApp = musicApps.contains(it.`package`),
isChecked = allowList.contains(it.`package`) || (!denyList.contains(it.`package`) && musicApps.contains(
it.`package`
packageName = it.componentName.packageName,
isMusicApp = musicApps.contains(it.componentName.packageName),
isChecked = allowList.contains(it.componentName.packageName) || (!denyList.contains(it.componentName.packageName) && musicApps.contains(
it.componentName.packageName
)),
icon = iconService.getIcon(it, (32 * density).roundToInt())
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(10000))
@@ -55,7 +55,7 @@ class EditTagSheetVM : ViewModel(), KoinComponent {
viewModelScope.launch(Dispatchers.Default) {
allTags = tagService.getAllTags().first().toSet()
val items = if (tag != null) tagService.getTaggedItems(tag).first() else emptyList()
val apps = appRepository.getAllInstalledApps().first().sorted()
val apps = appRepository.findMany().first().sorted()
taggedItems = items
taggableApps = apps.map { app -> TaggableItem(app, items.any { app.key == it.key }) }
taggableOther = items.mapNotNull { item ->