Annual refactor
This commit is contained in:
@@ -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
|
||||
|
||||
+30
-21
@@ -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
|
||||
|
||||
+23
-143
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
+16
-16
@@ -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()
|
||||
|
||||
+8
-6
@@ -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 ->
|
||||
|
||||
-1
@@ -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 {
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
+7
-17
@@ -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,
|
||||
|
||||
+7
-6
@@ -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 ->
|
||||
|
||||
Reference in New Issue
Block a user