Search result design adjustments
This commit is contained in:
parent
e41d20331f
commit
8875f6922b
47
.idea/emulatorDisplays.xml
generated
Normal file
47
.idea/emulatorDisplays.xml
generated
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="EmulatorDisplays">
|
||||||
|
<option name="displayStateByAvdFolder">
|
||||||
|
<map>
|
||||||
|
<entry key="$USER_HOME$/.android/avd/Pixel_7_API_34.avd">
|
||||||
|
<value>
|
||||||
|
<MultiDisplayState>
|
||||||
|
<option name="displayDescriptors">
|
||||||
|
<list>
|
||||||
|
<DisplayDescriptor>
|
||||||
|
<option name="height" value="2541" />
|
||||||
|
<option name="width" value="1200" />
|
||||||
|
</DisplayDescriptor>
|
||||||
|
<DisplayDescriptor>
|
||||||
|
<option name="displayId" value="1" />
|
||||||
|
<option name="height" value="1920" />
|
||||||
|
<option name="width" value="1080" />
|
||||||
|
</DisplayDescriptor>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="panelState">
|
||||||
|
<PanelState>
|
||||||
|
<option name="splitPanel">
|
||||||
|
<SplitPanelState>
|
||||||
|
<option name="proportion" value="0.5263158082962036" />
|
||||||
|
<option name="firstComponent">
|
||||||
|
<PanelState>
|
||||||
|
<option name="displayId" value="0" />
|
||||||
|
</PanelState>
|
||||||
|
</option>
|
||||||
|
<option name="secondComponent">
|
||||||
|
<PanelState>
|
||||||
|
<option name="displayId" value="1" />
|
||||||
|
</PanelState>
|
||||||
|
</option>
|
||||||
|
</SplitPanelState>
|
||||||
|
</option>
|
||||||
|
</PanelState>
|
||||||
|
</option>
|
||||||
|
</MultiDisplayState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@ -43,7 +43,6 @@ import org.koin.android.ext.koin.androidContext
|
|||||||
import org.koin.android.ext.koin.androidLogger
|
import org.koin.android.ext.koin.androidLogger
|
||||||
import org.koin.core.context.startKoin
|
import org.koin.core.context.startKoin
|
||||||
import org.koin.core.logger.Level
|
import org.koin.core.logger.Level
|
||||||
import java.text.Collator
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class LauncherApplication : Application(), CoroutineScope, ImageLoaderFactory {
|
class LauncherApplication : Application(), CoroutineScope, ImageLoaderFactory {
|
||||||
@ -109,12 +108,4 @@ class LauncherApplication : Application(), CoroutineScope, ImageLoaderFactory {
|
|||||||
.crossfade(200)
|
.crossfade(200)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
val collator: Collator by lazy {
|
|
||||||
Collator.getInstance().apply { strength = Collator.SECONDARY }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -2,6 +2,7 @@ package de.mm20.launcher2.ui.assistant
|
|||||||
|
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
@ -25,6 +26,7 @@ import de.mm20.launcher2.ui.launcher.gestures.LauncherGestureHandler
|
|||||||
import de.mm20.launcher2.ui.launcher.search.SearchColumn
|
import de.mm20.launcher2.ui.launcher.search.SearchColumn
|
||||||
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
||||||
import de.mm20.launcher2.ui.launcher.searchbar.LauncherSearchBar
|
import de.mm20.launcher2.ui.launcher.searchbar.LauncherSearchBar
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||||
import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper
|
import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
@ -112,6 +114,7 @@ fun AssistantScaffold(
|
|||||||
|
|
||||||
|
|
||||||
val colorSurface = MaterialTheme.colorScheme.surface
|
val colorSurface = MaterialTheme.colorScheme.surface
|
||||||
|
val isDarkTheme = LocalDarkTheme.current
|
||||||
LaunchedEffect(darkStatusBarIcons, colorSurface, showStatusBarScrim) {
|
LaunchedEffect(darkStatusBarIcons, colorSurface, showStatusBarScrim) {
|
||||||
if (showStatusBarScrim) {
|
if (showStatusBarScrim) {
|
||||||
systemUiController.setStatusBarColor(
|
systemUiController.setStatusBarColor(
|
||||||
@ -120,7 +123,7 @@ fun AssistantScaffold(
|
|||||||
} else {
|
} else {
|
||||||
systemUiController.setStatusBarColor(
|
systemUiController.setStatusBarColor(
|
||||||
Color.Transparent,
|
Color.Transparent,
|
||||||
darkIcons = darkStatusBarIcons
|
darkIcons = !isDarkTheme,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,8 +170,8 @@ fun AssistantScaffold(
|
|||||||
SearchColumn(
|
SearchColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
paddingValues = PaddingValues(
|
paddingValues = PaddingValues(
|
||||||
top = (if (bottomSearchBar) 0.dp else 56.dp + webSearchPadding) + 4.dp + windowInsets.calculateTopPadding(),
|
top = (if (bottomSearchBar) 0.dp else 64.dp + webSearchPadding) + 8.dp + windowInsets.calculateTopPadding(),
|
||||||
bottom = (if (bottomSearchBar) 56.dp + webSearchPadding else 0.dp) + 4.dp + windowInsets.calculateBottomPadding() + keyboardFilterBarPadding
|
bottom = (if (bottomSearchBar) 64.dp + webSearchPadding else 0.dp) + 8.dp + windowInsets.calculateBottomPadding() + keyboardFilterBarPadding
|
||||||
),
|
),
|
||||||
reverse = reverseSearchResults,
|
reverse = reverseSearchResults,
|
||||||
state = searchState
|
state = searchState
|
||||||
|
|||||||
@ -24,7 +24,7 @@ abstract class FavoritesVM : ViewModel(), KoinComponent {
|
|||||||
|
|
||||||
val selectedTag = MutableStateFlow<String?>(null)
|
val selectedTag = MutableStateFlow<String?>(null)
|
||||||
|
|
||||||
val showEditButton = settings.showEditButton
|
val showEditButton = settings.showEditButton.stateIn(viewModelScope, SharingStarted.Lazily, false)
|
||||||
abstract val tagsExpanded: Flow<Boolean>
|
abstract val tagsExpanded: Flow<Boolean>
|
||||||
|
|
||||||
val pinnedTags = favoritesService.getFavorites(
|
val pinnedTags = favoritesService.getFavorites(
|
||||||
|
|||||||
@ -80,7 +80,7 @@ fun SearchBar(
|
|||||||
when {
|
when {
|
||||||
it == SearchBarLevel.Resting && style != SearchBarStyle.Solid -> 0.dp
|
it == SearchBarLevel.Resting && style != SearchBarStyle.Solid -> 0.dp
|
||||||
it == SearchBarLevel.Raised -> 8.dp
|
it == SearchBarLevel.Raised -> 8.dp
|
||||||
else -> 2.dp
|
else -> 0.dp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
package de.mm20.launcher2.ui.ktx
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
val ButtonDefaults.TextButtonWithTrailingIconContentPadding
|
||||||
|
get() = PaddingValues(
|
||||||
|
start = 16.dp,
|
||||||
|
top = ContentPadding.calculateTopPadding(),
|
||||||
|
end = 12.dp,
|
||||||
|
bottom = ContentPadding.calculateBottomPadding()
|
||||||
|
)
|
||||||
@ -0,0 +1,138 @@
|
|||||||
|
package de.mm20.launcher2.ui.ktx
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.AnimationSpec
|
||||||
|
import androidx.compose.animation.core.AnimationVector4D
|
||||||
|
import androidx.compose.animation.core.TwoWayConverter
|
||||||
|
import androidx.compose.animation.core.animateValueAsState
|
||||||
|
import androidx.compose.animation.core.spring
|
||||||
|
import androidx.compose.foundation.shape.CornerBasedShape
|
||||||
|
import androidx.compose.foundation.shape.CornerSize
|
||||||
|
import androidx.compose.foundation.shape.CutCornerShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.unit.Density
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animates the corners of a shape between its original value and 0.
|
||||||
|
* For each parameter set to true, the corresponding corner will animate to the original value.
|
||||||
|
* Otherwise, it will animate to 0.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun CornerBasedShape.animateCorners(
|
||||||
|
topStart: Boolean,
|
||||||
|
topEnd: Boolean,
|
||||||
|
bottomEnd: Boolean,
|
||||||
|
bottomStart: Boolean,
|
||||||
|
): CornerBasedShape {
|
||||||
|
val value by animateShapeAsState(
|
||||||
|
copy(
|
||||||
|
topStart = if (topStart) this.topStart else CornerSize(0f),
|
||||||
|
topEnd = if (topEnd) this.topEnd else CornerSize(0f),
|
||||||
|
bottomEnd = if (bottomEnd) this.bottomEnd else CornerSize(0f),
|
||||||
|
bottomStart = if (bottomStart) this.bottomStart else CornerSize(0f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animate between two shapes.
|
||||||
|
* Limitations:
|
||||||
|
* - Only works for [RoundedCornerShape] and [CutCornerShape]
|
||||||
|
* - Shape type should be consistent (e.g. you can't animate between [RoundedCornerShape] and [CutCornerShape]), otherwise the animation will be incorrect
|
||||||
|
* - Doesn't support percentage based corner sizes
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun animateShapeAsState(
|
||||||
|
shape: CornerBasedShape,
|
||||||
|
animationSpec: AnimationSpec<CornerBasedShape> = remember { spring() },
|
||||||
|
visibilityThreshold: CornerBasedShape? = null,
|
||||||
|
label: String = "ValueAnimation",
|
||||||
|
finishedListener: ((CornerBasedShape) -> Unit)? = null
|
||||||
|
): State<CornerBasedShape> {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
val converter = remember(shape.javaClass, density) {
|
||||||
|
if (shape is CutCornerShape) CutCornerShapeConverter(density)
|
||||||
|
else RoundedCornerShapeConverter(density)
|
||||||
|
}
|
||||||
|
return animateValueAsState(
|
||||||
|
shape,
|
||||||
|
typeConverter = converter,
|
||||||
|
animationSpec = animationSpec,
|
||||||
|
visibilityThreshold = visibilityThreshold,
|
||||||
|
label = label,
|
||||||
|
finishedListener = finishedListener
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RoundedCornerShapeConverter(
|
||||||
|
private val density: Density,
|
||||||
|
) : TwoWayConverter<CornerBasedShape, AnimationVector4D> {
|
||||||
|
override val convertFromVector: (AnimationVector4D) -> CornerBasedShape
|
||||||
|
get() = {
|
||||||
|
RoundedCornerShape(
|
||||||
|
topStart = it.v1,
|
||||||
|
topEnd = it.v2,
|
||||||
|
bottomEnd = it.v3,
|
||||||
|
bottomStart = it.v4
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override val convertToVector: (CornerBasedShape) -> AnimationVector4D
|
||||||
|
get() = {
|
||||||
|
AnimationVector4D(
|
||||||
|
it.topStart.toPx(Size.Zero, density),
|
||||||
|
it.topEnd.toPx(Size.Zero, density),
|
||||||
|
it.bottomEnd.toPx(Size.Zero, density),
|
||||||
|
it.bottomStart.toPx(Size.Zero, density)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CutCornerShapeConverter(
|
||||||
|
private val density: Density,
|
||||||
|
) : TwoWayConverter<CornerBasedShape, AnimationVector4D> {
|
||||||
|
override val convertFromVector: (AnimationVector4D) -> CornerBasedShape
|
||||||
|
get() = {
|
||||||
|
CutCornerShape(
|
||||||
|
topStart = it.v1,
|
||||||
|
topEnd = it.v2,
|
||||||
|
bottomEnd = it.v3,
|
||||||
|
bottomStart = it.v4
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override val convertToVector: (CornerBasedShape) -> AnimationVector4D
|
||||||
|
get() = {
|
||||||
|
AnimationVector4D(
|
||||||
|
it.topStart.toPx(Size.Zero, density),
|
||||||
|
it.topEnd.toPx(Size.Zero, density),
|
||||||
|
it.bottomEnd.toPx(Size.Zero, density),
|
||||||
|
it.bottomStart.toPx(Size.Zero, density)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CornerBasedShape.withCorners(
|
||||||
|
topStart: Boolean = true,
|
||||||
|
topEnd: Boolean = true,
|
||||||
|
bottomEnd: Boolean = true,
|
||||||
|
bottomStart: Boolean = true
|
||||||
|
): Shape {
|
||||||
|
if (topStart && topEnd && bottomEnd && bottomStart) return this
|
||||||
|
if (!topStart && !topEnd && !bottomEnd && !bottomStart) return RectangleShape
|
||||||
|
return copy(
|
||||||
|
topStart = if (topStart) this.topStart else CornerSize(0f),
|
||||||
|
topEnd = if (topEnd) this.topEnd else CornerSize(0f),
|
||||||
|
bottomEnd = if (bottomEnd) this.bottomEnd else CornerSize(0f),
|
||||||
|
bottomStart = if (bottomStart) this.bottomStart else CornerSize(0f)
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.requiredWidth
|
|||||||
import androidx.compose.foundation.layout.safeDrawing
|
import androidx.compose.foundation.layout.safeDrawing
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.PagerDefaults
|
import androidx.compose.foundation.pager.PagerDefaults
|
||||||
@ -56,6 +57,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.composed
|
import androidx.compose.ui.composed
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
@ -96,6 +98,7 @@ import de.mm20.launcher2.ui.launcher.search.SearchVM
|
|||||||
import de.mm20.launcher2.ui.launcher.searchbar.LauncherSearchBar
|
import de.mm20.launcher2.ui.launcher.searchbar.LauncherSearchBar
|
||||||
import de.mm20.launcher2.ui.launcher.widgets.WidgetColumn
|
import de.mm20.launcher2.ui.launcher.widgets.WidgetColumn
|
||||||
import de.mm20.launcher2.ui.launcher.widgets.clock.ClockWidget
|
import de.mm20.launcher2.ui.launcher.widgets.clock.ClockWidget
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||||
import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper
|
import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
@ -192,7 +195,8 @@ fun PagerScaffold(
|
|||||||
val systemUiController = rememberSystemUiController()
|
val systemUiController = rememberSystemUiController()
|
||||||
|
|
||||||
val colorSurface = MaterialTheme.colorScheme.surface
|
val colorSurface = MaterialTheme.colorScheme.surface
|
||||||
LaunchedEffect(isWidgetEditMode, darkStatusBarIcons, colorSurface, showStatusBarScrim) {
|
val isDarkTheme = LocalDarkTheme.current
|
||||||
|
LaunchedEffect(isWidgetEditMode, darkStatusBarIcons, colorSurface, showStatusBarScrim, isSearchOpen) {
|
||||||
if (isWidgetEditMode) {
|
if (isWidgetEditMode) {
|
||||||
systemUiController.setStatusBarColor(
|
systemUiController.setStatusBarColor(
|
||||||
colorSurface
|
colorSurface
|
||||||
@ -201,6 +205,11 @@ fun PagerScaffold(
|
|||||||
systemUiController.setStatusBarColor(
|
systemUiController.setStatusBarColor(
|
||||||
colorSurface.copy(0.7f),
|
colorSurface.copy(0.7f),
|
||||||
)
|
)
|
||||||
|
} else if (isSearchOpen) {
|
||||||
|
systemUiController.setStatusBarColor(
|
||||||
|
Color.Transparent,
|
||||||
|
darkIcons = !isDarkTheme,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
systemUiController.setStatusBarColor(
|
systemUiController.setStatusBarColor(
|
||||||
Color.Transparent,
|
Color.Transparent,
|
||||||
@ -293,8 +302,6 @@ fun PagerScaffold(
|
|||||||
handleBackOrHomeEvent()
|
handleBackOrHomeEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
|
||||||
|
|
||||||
val gestureManager = LocalGestureDetector.current
|
val gestureManager = LocalGestureDetector.current
|
||||||
|
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
@ -395,10 +402,16 @@ fun PagerScaffold(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
val minFlingVelocity = 1000.dp.toPixels()
|
val minFlingVelocity = 1000.dp.toPixels()
|
||||||
|
val colorSurfaceContainer = MaterialTheme.colorScheme.surfaceContainer
|
||||||
|
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
.drawBehind {
|
||||||
|
drawRect(
|
||||||
|
color = colorSurfaceContainer.copy(alpha = -pagerState.getOffsetDistanceInPages(0) * 0.85f),
|
||||||
|
)
|
||||||
|
}
|
||||||
.nestedScroll(pagerNestedScrollConnection),
|
.nestedScroll(pagerNestedScrollConnection),
|
||||||
beyondViewportPageCount = 1,
|
beyondViewportPageCount = 1,
|
||||||
reverseLayout = reverse == (LocalLayoutDirection.current == LayoutDirection.Ltr),
|
reverseLayout = reverse == (LocalLayoutDirection.current == LayoutDirection.Ltr),
|
||||||
@ -514,13 +527,13 @@ fun PagerScaffold(
|
|||||||
val windowInsets = WindowInsets.safeDrawing.asPaddingValues()
|
val windowInsets = WindowInsets.safeDrawing.asPaddingValues()
|
||||||
val paddingValues = if (bottomSearchBar) {
|
val paddingValues = if (bottomSearchBar) {
|
||||||
PaddingValues(
|
PaddingValues(
|
||||||
top = 4.dp + windowInsets.calculateTopPadding(),
|
top = 8.dp + windowInsets.calculateTopPadding(),
|
||||||
bottom = 60.dp + webSearchPadding + windowInsets.calculateBottomPadding() + keyboardFilterBarPadding
|
bottom = 64.dp + webSearchPadding + windowInsets.calculateBottomPadding() + keyboardFilterBarPadding
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
PaddingValues(
|
PaddingValues(
|
||||||
bottom = 4.dp + windowInsets.calculateBottomPadding() + keyboardFilterBarPadding,
|
bottom = 8.dp + windowInsets.calculateBottomPadding() + keyboardFilterBarPadding,
|
||||||
top = 60.dp + webSearchPadding + windowInsets.calculateTopPadding()
|
top = 64.dp + webSearchPadding + windowInsets.calculateTopPadding()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SearchColumn(
|
SearchColumn(
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import androidx.compose.animation.core.spring
|
|||||||
import androidx.compose.animation.slideIn
|
import androidx.compose.animation.slideIn
|
||||||
import androidx.compose.animation.slideOut
|
import androidx.compose.animation.slideOut
|
||||||
import androidx.compose.foundation.LocalOverscrollConfiguration
|
import androidx.compose.foundation.LocalOverscrollConfiguration
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
|
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@ -27,6 +28,7 @@ import androidx.compose.foundation.layout.offset
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.safeDrawing
|
import androidx.compose.foundation.layout.safeDrawing
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.pager.VerticalPager
|
import androidx.compose.foundation.pager.VerticalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
@ -50,6 +52,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.TransformOrigin
|
import androidx.compose.ui.graphics.TransformOrigin
|
||||||
@ -80,6 +83,7 @@ import de.mm20.launcher2.ui.launcher.search.SearchVM
|
|||||||
import de.mm20.launcher2.ui.launcher.searchbar.LauncherSearchBar
|
import de.mm20.launcher2.ui.launcher.searchbar.LauncherSearchBar
|
||||||
import de.mm20.launcher2.ui.launcher.widgets.WidgetColumn
|
import de.mm20.launcher2.ui.launcher.widgets.WidgetColumn
|
||||||
import de.mm20.launcher2.ui.launcher.widgets.clock.ClockWidget
|
import de.mm20.launcher2.ui.launcher.widgets.clock.ClockWidget
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||||
import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper
|
import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
@ -194,7 +198,8 @@ fun PullDownScaffold(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val colorSurface = MaterialTheme.colorScheme.surface
|
val colorSurface = MaterialTheme.colorScheme.surface
|
||||||
LaunchedEffect(isWidgetEditMode, darkStatusBarIcons, colorSurface, showStatusBarScrim) {
|
val isDarkTheme = LocalDarkTheme.current
|
||||||
|
LaunchedEffect(isWidgetEditMode, darkStatusBarIcons, colorSurface, showStatusBarScrim, isSearchOpen) {
|
||||||
if (isWidgetEditMode) {
|
if (isWidgetEditMode) {
|
||||||
systemUiController.setStatusBarColor(
|
systemUiController.setStatusBarColor(
|
||||||
colorSurface
|
colorSurface
|
||||||
@ -203,6 +208,11 @@ fun PullDownScaffold(
|
|||||||
systemUiController.setStatusBarColor(
|
systemUiController.setStatusBarColor(
|
||||||
colorSurface.copy(0.7f),
|
colorSurface.copy(0.7f),
|
||||||
)
|
)
|
||||||
|
} else if (isSearchOpen) {
|
||||||
|
systemUiController.setStatusBarColor(
|
||||||
|
Color.Transparent,
|
||||||
|
darkIcons = !isDarkTheme,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
systemUiController.setStatusBarColor(
|
systemUiController.setStatusBarColor(
|
||||||
Color.Transparent,
|
Color.Transparent,
|
||||||
@ -376,8 +386,14 @@ fun PullDownScaffold(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val insets = WindowInsets.safeDrawing.asPaddingValues()
|
val insets = WindowInsets.safeDrawing.asPaddingValues()
|
||||||
|
val colorSurfaceContainer = MaterialTheme.colorScheme.surfaceContainer
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
.drawBehind {
|
||||||
|
drawRect(
|
||||||
|
color = colorSurfaceContainer.copy(alpha = -pagerState.getOffsetDistanceInPages(0) * 0.85f),
|
||||||
|
)
|
||||||
|
}
|
||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectHorizontalDragGestures(
|
detectHorizontalDragGestures(
|
||||||
onDragEnd = {
|
onDragEnd = {
|
||||||
@ -402,7 +418,8 @@ fun PullDownScaffold(
|
|||||||
LocalOverscrollConfiguration provides null
|
LocalOverscrollConfiguration provides null
|
||||||
) {
|
) {
|
||||||
VerticalPager(
|
VerticalPager(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier
|
||||||
|
.fillMaxSize(),
|
||||||
beyondViewportPageCount = 1,
|
beyondViewportPageCount = 1,
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
reverseLayout = true,
|
reverseLayout = true,
|
||||||
@ -518,10 +535,10 @@ fun PullDownScaffold(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
paddingValues = PaddingValues(
|
paddingValues = PaddingValues(
|
||||||
top = windowInsets.calculateTopPadding() + if (!bottomSearchBar) 60.dp + webSearchPadding else 4.dp,
|
top = windowInsets.calculateTopPadding() + if (!bottomSearchBar) 64.dp + webSearchPadding else 8.dp,
|
||||||
bottom = windowInsets.calculateBottomPadding() +
|
bottom = windowInsets.calculateBottomPadding() +
|
||||||
keyboardFilterBarPadding +
|
keyboardFilterBarPadding +
|
||||||
if (bottomSearchBar) 60.dp + webSearchPadding else 4.dp
|
if (bottomSearchBar) 64.dp + webSearchPadding else 8.dp
|
||||||
),
|
),
|
||||||
state = searchState,
|
state = searchState,
|
||||||
reverse = reverseSearchResults,
|
reverse = reverseSearchResults,
|
||||||
@ -596,7 +613,7 @@ fun PullDownScaffold(
|
|||||||
searchBarOffset = {
|
searchBarOffset = {
|
||||||
(if (searchBarFocused || fixedSearchBar) 0 else searchBarOffset.value.toInt() * (if (bottomSearchBar) 1 else -1)) +
|
(if (searchBarFocused || fixedSearchBar) 0 else searchBarOffset.value.toInt() * (if (bottomSearchBar) 1 else -1)) +
|
||||||
with(density) {
|
with(density) {
|
||||||
(editModeSearchBarOffset - if(bottomSearchBar) keyboardFilterBarPadding else 0.dp)
|
(editModeSearchBarOffset - if (bottomSearchBar) keyboardFilterBarPadding else 0.dp)
|
||||||
.toPx()
|
.toPx()
|
||||||
.roundToInt()
|
.roundToInt()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package de.mm20.launcher2.ui.launcher.search
|
|||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@ -10,27 +11,19 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyItemScope
|
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
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.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@ -38,35 +31,42 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clipToBounds
|
import androidx.compose.ui.draw.clipToBounds
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.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.Contact
|
||||||
|
import de.mm20.launcher2.search.File
|
||||||
|
import de.mm20.launcher2.search.Location
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.search.Website
|
||||||
import de.mm20.launcher2.ui.common.FavoritesTagSelector
|
|
||||||
import de.mm20.launcher2.ui.component.Banner
|
|
||||||
import de.mm20.launcher2.ui.component.LauncherCard
|
import de.mm20.launcher2.ui.component.LauncherCard
|
||||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
|
||||||
import de.mm20.launcher2.ui.component.PartialLauncherCard
|
import de.mm20.launcher2.ui.component.PartialLauncherCard
|
||||||
import de.mm20.launcher2.ui.launcher.search.calculator.CalculatorItem
|
import de.mm20.launcher2.ui.launcher.search.apps.AppResults
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.calculator.CalculatorResults
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.calendar.CalendarResults
|
||||||
import de.mm20.launcher2.ui.launcher.search.common.grid.GridItem
|
import de.mm20.launcher2.ui.launcher.search.common.grid.GridItem
|
||||||
import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.contacts.ContactResults
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.favorites.SearchFavorites
|
||||||
import de.mm20.launcher2.ui.launcher.search.favorites.SearchFavoritesVM
|
import de.mm20.launcher2.ui.launcher.search.favorites.SearchFavoritesVM
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.files.FileResults
|
||||||
import de.mm20.launcher2.ui.launcher.search.filters.SearchFilters
|
import de.mm20.launcher2.ui.launcher.search.filters.SearchFilters
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.location.LocationResults
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.shortcut.ShortcutResults
|
||||||
import de.mm20.launcher2.ui.launcher.search.unitconverter.UnitConverterItem
|
import de.mm20.launcher2.ui.launcher.search.unitconverter.UnitConverterItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.unitconverter.UnitConverterResults
|
||||||
import de.mm20.launcher2.ui.launcher.search.website.WebsiteItem
|
import de.mm20.launcher2.ui.launcher.search.website.WebsiteItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.website.WebsiteResults
|
||||||
import de.mm20.launcher2.ui.launcher.search.wikipedia.ArticleItem
|
import de.mm20.launcher2.ui.launcher.search.wikipedia.ArticleItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.wikipedia.ArticleResults
|
||||||
import de.mm20.launcher2.ui.launcher.sheets.HiddenItemsSheet
|
import de.mm20.launcher2.ui.launcher.sheets.HiddenItemsSheet
|
||||||
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
|
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
|
||||||
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
||||||
import de.mm20.launcher2.ui.locals.LocalGridSettings
|
import de.mm20.launcher2.ui.locals.LocalGridSettings
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
|
||||||
import kotlin.math.ceil
|
|
||||||
|
|
||||||
private const val PRIORITY_MIN = Int.MAX_VALUE
|
|
||||||
private const val PRIORITY_MAX = Int.MIN_VALUE
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchColumn(
|
fun SearchColumn(
|
||||||
@ -85,8 +85,6 @@ fun SearchColumn(
|
|||||||
val favoritesVM: SearchFavoritesVM = viewModel()
|
val favoritesVM: SearchFavoritesVM = viewModel()
|
||||||
val favorites by favoritesVM.favorites.collectAsState(emptyList())
|
val favorites by favoritesVM.favorites.collectAsState(emptyList())
|
||||||
|
|
||||||
var showWorkProfileApps by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val hideFavs by viewModel.hideFavorites
|
val hideFavs by viewModel.hideFavorites
|
||||||
val favoritesEnabled by viewModel.favoritesEnabled.collectAsState(false)
|
val favoritesEnabled by viewModel.favoritesEnabled.collectAsState(false)
|
||||||
val apps by viewModel.appResults
|
val apps by viewModel.appResults
|
||||||
@ -101,7 +99,6 @@ fun SearchColumn(
|
|||||||
val locations by viewModel.locationResults
|
val locations by viewModel.locationResults
|
||||||
val website by viewModel.websiteResults
|
val website by viewModel.websiteResults
|
||||||
val hiddenResults by viewModel.hiddenResults
|
val hiddenResults by viewModel.hiddenResults
|
||||||
val separateWorkProfile by viewModel.separateWorkProfile.collectAsState(true)
|
|
||||||
|
|
||||||
val bestMatch by viewModel.bestMatch
|
val bestMatch by viewModel.bestMatch
|
||||||
|
|
||||||
@ -119,284 +116,199 @@ fun SearchColumn(
|
|||||||
val favoritesEditButton by favoritesVM.showEditButton.collectAsState(false)
|
val favoritesEditButton by favoritesVM.showEditButton.collectAsState(false)
|
||||||
val favoritesTagsExpanded by favoritesVM.tagsExpanded.collectAsState(false)
|
val favoritesTagsExpanded by favoritesVM.tagsExpanded.collectAsState(false)
|
||||||
|
|
||||||
|
var showWorkProfileApps by remember { mutableStateOf(false) }
|
||||||
|
val separateWorkProfile by viewModel.separateWorkProfile.collectAsState(true)
|
||||||
|
val visibleApps by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
when {
|
||||||
|
!separateWorkProfile -> (apps + workApps).sorted()
|
||||||
|
workApps.isEmpty() -> apps
|
||||||
|
apps.isEmpty() -> workApps
|
||||||
|
showWorkProfileApps -> workApps
|
||||||
|
else -> apps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var expandedCategory: SearchCategory? by remember(isSearchEmpty) { mutableStateOf(null) }
|
||||||
|
|
||||||
|
var selectedContactIndex: Int by remember(contacts) { mutableIntStateOf(-1) }
|
||||||
|
var selectedFileIndex: Int by remember(files) { mutableIntStateOf(-1) }
|
||||||
|
var selectedCalendarIndex: Int by remember(events) { mutableIntStateOf(-1) }
|
||||||
|
var selectedLocationIndex: Int by remember(events) { mutableIntStateOf(-1) }
|
||||||
|
var selectedShortcutIndex: Int by remember(events) { mutableIntStateOf(-1) }
|
||||||
|
var selectedArticleIndex: Int by remember(events) { mutableIntStateOf(-1) }
|
||||||
|
var selectedWebsiteIndex: Int by remember(events) { mutableIntStateOf(-1) }
|
||||||
|
|
||||||
val showFilters by viewModel.showFilters
|
val showFilters by viewModel.showFilters
|
||||||
|
|
||||||
AnimatedContent(showFilters) {
|
AnimatedContent(
|
||||||
|
showFilters,
|
||||||
|
modifier = modifier.padding(horizontal = 8.dp),
|
||||||
|
) {
|
||||||
if (it) {
|
if (it) {
|
||||||
BackHandler {
|
BackHandler {
|
||||||
viewModel.showFilters.value = false
|
viewModel.showFilters.value = false
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues),
|
.padding(paddingValues),
|
||||||
contentAlignment = if (reverse) Alignment.BottomCenter else Alignment.TopCenter,
|
contentAlignment = if (reverse) Alignment.BottomCenter else Alignment.TopCenter,
|
||||||
) {
|
) {
|
||||||
LauncherCard(
|
SearchFilters(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
.padding(top = 4.dp)
|
||||||
) {
|
.background(
|
||||||
SearchFilters(
|
MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp),
|
||||||
modifier = Modifier
|
MaterialTheme.shapes.medium
|
||||||
.fillMaxWidth()
|
)
|
||||||
.padding(12.dp),
|
.padding(12.dp),
|
||||||
filters = viewModel.filters.value,
|
filters = viewModel.filters.value,
|
||||||
onFiltersChange = {
|
onFiltersChange = {
|
||||||
viewModel.setFilters(it)
|
viewModel.setFilters(it)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = state,
|
state = state,
|
||||||
modifier = modifier,
|
|
||||||
userScrollEnabled = userScrollEnabled,
|
userScrollEnabled = userScrollEnabled,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
reverseLayout = reverse,
|
reverseLayout = reverse,
|
||||||
) {
|
) {
|
||||||
if (!hideFavs && favoritesEnabled) {
|
if (!hideFavs && favoritesEnabled) {
|
||||||
GridResults(
|
SearchFavorites(
|
||||||
items = favorites.toImmutableList(),
|
favorites = favorites,
|
||||||
columns = columns,
|
selectedTag = selectedTag,
|
||||||
key = "favorites",
|
pinnedTags = pinnedTags,
|
||||||
|
tagsExpanded = favoritesTagsExpanded,
|
||||||
|
onSelectTag = { favoritesVM.selectTag(it) },
|
||||||
reverse = reverse,
|
reverse = reverse,
|
||||||
before = if (favorites.isEmpty()) {
|
onExpandTags = {
|
||||||
{
|
favoritesVM.setTagsExpanded(it)
|
||||||
Banner(
|
|
||||||
modifier = Modifier.padding(16.dp),
|
|
||||||
text = stringResource(
|
|
||||||
if (selectedTag == null) R.string.favorites_empty else R.string.favorites_empty_tag
|
|
||||||
),
|
|
||||||
icon = if (selectedTag == null) Icons.Rounded.Star else Icons.Rounded.Tag,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else null,
|
|
||||||
after = if (pinnedTags.isEmpty() && !favoritesEditButton) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
{
|
|
||||||
FavoritesTagSelector(
|
|
||||||
tags = pinnedTags,
|
|
||||||
selectedTag = selectedTag,
|
|
||||||
editButton = favoritesEditButton,
|
|
||||||
reverse = reverse,
|
|
||||||
onSelectTag = { favoritesVM.selectTag(it) },
|
|
||||||
scrollState = tagsScrollState,
|
|
||||||
expanded = favoritesTagsExpanded,
|
|
||||||
onExpand = { favoritesVM.setTagsExpanded(it) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
highlightedItem = bestMatch as? SavableSearchable
|
editButton = favoritesEditButton
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
GridResults(
|
AppResults(
|
||||||
items = if (separateWorkProfile) if ((showWorkProfileApps || apps.isEmpty()) && workApps.isNotEmpty()) workApps.toImmutableList() else apps.toImmutableList() else listOf(
|
apps = visibleApps,
|
||||||
apps,
|
showTabs = separateWorkProfile && apps.isNotEmpty() && workApps.isNotEmpty(),
|
||||||
workApps
|
highlightedItem = bestMatch as? Application,
|
||||||
).flatten().sorted().toImmutableList(),
|
selectedTab = if (showWorkProfileApps) 1 else 0,
|
||||||
|
onSelectedTabChange = { showWorkProfileApps = it == 1 },
|
||||||
columns = columns,
|
columns = columns,
|
||||||
reverse = reverse,
|
reverse = reverse
|
||||||
key = "apps",
|
|
||||||
before = if (separateWorkProfile && workApps.isNotEmpty() && apps.isNotEmpty()) {
|
|
||||||
{
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 8.dp)
|
|
||||||
.padding(
|
|
||||||
top = if (reverse) 4.dp else 8.dp,
|
|
||||||
bottom = if (reverse) 8.dp else 4.dp
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
FilterChip(
|
|
||||||
modifier = Modifier.padding(horizontal = 8.dp),
|
|
||||||
selected = !showWorkProfileApps,
|
|
||||||
onClick = { showWorkProfileApps = false },
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.Person,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.apps_profile_main),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
FilterChip(
|
|
||||||
selected = showWorkProfileApps,
|
|
||||||
onClick = { showWorkProfileApps = true },
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.Work,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.apps_profile_work),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else null,
|
|
||||||
highlightedItem = bestMatch as? SavableSearchable
|
|
||||||
)
|
)
|
||||||
ListResults(
|
|
||||||
before = if (missingShortcutsPermission && !isSearchEmpty) {
|
if (!isSearchEmpty) {
|
||||||
{
|
|
||||||
MissingPermissionBanner(
|
ShortcutResults(
|
||||||
modifier = Modifier.padding(8.dp),
|
shortcuts = appShortcuts,
|
||||||
text = stringResource(
|
missingPermission = missingShortcutsPermission,
|
||||||
R.string.missing_permission_appshortcuts_search,
|
onPermissionRequest = {
|
||||||
stringResource(R.string.app_name)
|
viewModel.requestAppShortcutPermission(context as AppCompatActivity)
|
||||||
),
|
},
|
||||||
onClick = { viewModel.requestAppShortcutPermission(context as AppCompatActivity) },
|
onPermissionRequestRejected = {
|
||||||
secondaryAction = {
|
viewModel.disableAppShortcutSearch()
|
||||||
OutlinedButton(onClick = {
|
},
|
||||||
viewModel.disableAppShortcutSearch()
|
reverse = reverse,
|
||||||
}) {
|
selectedIndex = selectedShortcutIndex,
|
||||||
Text(
|
onSelect = { selectedShortcutIndex = it },
|
||||||
stringResource(R.string.turn_off),
|
highlightedItem = bestMatch as? AppShortcut,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
UnitConverterResults(
|
||||||
)
|
converters = unitConverter,
|
||||||
|
reverse = reverse,
|
||||||
|
truncate = expandedCategory != SearchCategory.UnitConverter,
|
||||||
|
onShowAll = {
|
||||||
|
expandedCategory = SearchCategory.UnitConverter
|
||||||
}
|
}
|
||||||
} else null,
|
)
|
||||||
items = appShortcuts.toImmutableList(),
|
|
||||||
reverse = reverse,
|
CalculatorResults(
|
||||||
key = "shortcuts",
|
calculator,
|
||||||
highlightedItem = bestMatch as? SavableSearchable
|
reverse = reverse
|
||||||
)
|
)
|
||||||
for (conv in unitConverter) {
|
|
||||||
SingleResult {
|
CalendarResults(
|
||||||
UnitConverterItem(unitConverter = conv)
|
events = events,
|
||||||
}
|
missingPermission = missingCalendarPermission,
|
||||||
|
onPermissionRequest = {
|
||||||
|
viewModel.requestCalendarPermission(context as AppCompatActivity)
|
||||||
|
},
|
||||||
|
onPermissionRequestRejected = {
|
||||||
|
viewModel.disableCalendarSearch()
|
||||||
|
},
|
||||||
|
reverse = reverse,
|
||||||
|
selectedIndex = selectedCalendarIndex,
|
||||||
|
onSelect = { selectedCalendarIndex = it },
|
||||||
|
highlightedItem = bestMatch as? CalendarEvent,
|
||||||
|
)
|
||||||
|
|
||||||
|
ContactResults(
|
||||||
|
contacts = contacts,
|
||||||
|
missingPermission = missingContactsPermission,
|
||||||
|
onPermissionRequest = {
|
||||||
|
viewModel.requestContactsPermission(context as AppCompatActivity)
|
||||||
|
},
|
||||||
|
onPermissionRequestRejected = {
|
||||||
|
viewModel.disableContactsSearch()
|
||||||
|
},
|
||||||
|
reverse = reverse,
|
||||||
|
selectedIndex = selectedContactIndex,
|
||||||
|
onSelect = { selectedContactIndex = it },
|
||||||
|
highlightedItem = bestMatch as? Contact,
|
||||||
|
)
|
||||||
|
|
||||||
|
LocationResults(
|
||||||
|
locations = locations,
|
||||||
|
missingPermission = missingLocationPermission,
|
||||||
|
onPermissionRequest = {
|
||||||
|
viewModel.requestLocationPermission(context as AppCompatActivity)
|
||||||
|
},
|
||||||
|
onPermissionRequestRejected = {
|
||||||
|
viewModel.disableLocationSearch()
|
||||||
|
},
|
||||||
|
reverse = reverse,
|
||||||
|
selectedIndex = selectedLocationIndex,
|
||||||
|
onSelect = { selectedLocationIndex = it },
|
||||||
|
highlightedItem = bestMatch as? Location,
|
||||||
|
)
|
||||||
|
ArticleResults(
|
||||||
|
articles = wikipedia,
|
||||||
|
selectedIndex = selectedArticleIndex,
|
||||||
|
onSelect = { selectedArticleIndex = it },
|
||||||
|
highlightedItem = bestMatch as? Article,
|
||||||
|
reverse = reverse,
|
||||||
|
)
|
||||||
|
WebsiteResults(
|
||||||
|
websites = website,
|
||||||
|
selectedIndex = selectedWebsiteIndex,
|
||||||
|
onSelect = { selectedWebsiteIndex = it },
|
||||||
|
highlightedItem = bestMatch as? Website,
|
||||||
|
reverse = reverse,
|
||||||
|
)
|
||||||
|
FileResults(
|
||||||
|
files = files,
|
||||||
|
onPermissionRequest = {
|
||||||
|
viewModel.requestFilesPermission(context as AppCompatActivity)
|
||||||
|
},
|
||||||
|
onPermissionRequestRejected = {
|
||||||
|
viewModel.disableFilesSearch()
|
||||||
|
},
|
||||||
|
reverse = reverse,
|
||||||
|
highlightedItem = bestMatch as? File,
|
||||||
|
missingPermission = missingFilesPermission,
|
||||||
|
selectedIndex = selectedFileIndex,
|
||||||
|
onSelect = {
|
||||||
|
selectedFileIndex = it
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
for (calc in calculator) {
|
|
||||||
SingleResult {
|
|
||||||
CalculatorItem(calculator = calc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ListResults(
|
|
||||||
before = if (missingCalendarPermission && !isSearchEmpty) {
|
|
||||||
{
|
|
||||||
MissingPermissionBanner(
|
|
||||||
modifier = Modifier.padding(8.dp),
|
|
||||||
text = stringResource(R.string.missing_permission_calendar_search),
|
|
||||||
onClick = { viewModel.requestCalendarPermission(context as AppCompatActivity) },
|
|
||||||
secondaryAction = {
|
|
||||||
OutlinedButton(onClick = {
|
|
||||||
viewModel.disableCalendarSearch()
|
|
||||||
}) {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.turn_off),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else null,
|
|
||||||
items = events.toImmutableList(),
|
|
||||||
reverse = reverse,
|
|
||||||
key = "events",
|
|
||||||
highlightedItem = bestMatch as? SavableSearchable
|
|
||||||
)
|
|
||||||
ListResults(
|
|
||||||
before = if (missingContactsPermission && !isSearchEmpty) {
|
|
||||||
{
|
|
||||||
MissingPermissionBanner(
|
|
||||||
modifier = Modifier.padding(8.dp),
|
|
||||||
text = stringResource(R.string.missing_permission_contact_search),
|
|
||||||
onClick = { viewModel.requestContactsPermission(context as AppCompatActivity) },
|
|
||||||
secondaryAction = {
|
|
||||||
OutlinedButton(onClick = {
|
|
||||||
viewModel.disableContactsSearch()
|
|
||||||
}) {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.turn_off),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else null,
|
|
||||||
items = contacts.toImmutableList(),
|
|
||||||
reverse = reverse,
|
|
||||||
key = "contacts",
|
|
||||||
highlightedItem = bestMatch as? SavableSearchable
|
|
||||||
)
|
|
||||||
ListResults(
|
|
||||||
before = if (missingLocationPermission && !isSearchEmpty) {
|
|
||||||
{
|
|
||||||
MissingPermissionBanner(
|
|
||||||
modifier = Modifier.padding(8.dp),
|
|
||||||
text = stringResource(R.string.missing_permission_location_search),
|
|
||||||
onClick = { viewModel.requestLocationPermission(context as AppCompatActivity) },
|
|
||||||
secondaryAction = {
|
|
||||||
OutlinedButton(onClick = {
|
|
||||||
viewModel.disableLocationSearch()
|
|
||||||
}) {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.turn_off),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else null,
|
|
||||||
items = locations.toImmutableList(),
|
|
||||||
reverse = reverse,
|
|
||||||
key = "locations",
|
|
||||||
highlightedItem = bestMatch as? SavableSearchable
|
|
||||||
)
|
|
||||||
for (wiki in wikipedia) {
|
|
||||||
SingleResult(highlight = bestMatch == wiki) {
|
|
||||||
ArticleItem(article = wiki)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (ws in website) {
|
|
||||||
SingleResult(highlight = bestMatch == ws) {
|
|
||||||
WebsiteItem(website = ws)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ListResults(
|
|
||||||
before = if (missingFilesPermission && !isSearchEmpty) {
|
|
||||||
{
|
|
||||||
MissingPermissionBanner(
|
|
||||||
modifier = Modifier.padding(8.dp),
|
|
||||||
text = stringResource(R.string.missing_permission_files_search),
|
|
||||||
onClick = { viewModel.requestFilesPermission(context as AppCompatActivity) },
|
|
||||||
secondaryAction = {
|
|
||||||
OutlinedButton(onClick = {
|
|
||||||
viewModel.disableFilesSearch()
|
|
||||||
}) {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.turn_off),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else null,
|
|
||||||
items = files.toImmutableList(),
|
|
||||||
reverse = reverse,
|
|
||||||
key = "files",
|
|
||||||
highlightedItem = bestMatch as? SavableSearchable
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,170 +323,6 @@ fun SearchColumn(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LazyListScope.GridResults(
|
|
||||||
items: ImmutableList<SavableSearchable>,
|
|
||||||
columns: Int,
|
|
||||||
reverse: Boolean,
|
|
||||||
key: String,
|
|
||||||
before: (@Composable () -> Unit)? = null,
|
|
||||||
after: (@Composable () -> Unit)? = null,
|
|
||||||
highlightedItem: SavableSearchable?
|
|
||||||
) {
|
|
||||||
if (items.isEmpty() && before == null && after == null) return
|
|
||||||
|
|
||||||
if (before != null) {
|
|
||||||
item(key = "$key-before") {
|
|
||||||
PartialCardRow(
|
|
||||||
isFirst = true,
|
|
||||||
isLast = items.isEmpty() && after == null,
|
|
||||||
reverse = reverse
|
|
||||||
) {
|
|
||||||
before()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val rows = ceil(items.size / columns.toFloat()).toInt()
|
|
||||||
items(
|
|
||||||
rows,
|
|
||||||
key = {
|
|
||||||
"$key-${items[it * columns].key}"
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
PartialCardRow(
|
|
||||||
isFirst = it == 0 && before == null,
|
|
||||||
isLast = it == rows - 1 && after == null,
|
|
||||||
reverse = reverse
|
|
||||||
) {
|
|
||||||
GridRow(
|
|
||||||
modifier = Modifier.padding(
|
|
||||||
top = if (if (reverse) it == rows - 1 else it == 0) 4.dp else 0.dp,
|
|
||||||
bottom = if (if (reverse) it == 0 else it == rows - 1) 2.dp else 0.dp,
|
|
||||||
),
|
|
||||||
items = items.subList(
|
|
||||||
it * columns,
|
|
||||||
(it * columns + columns).coerceAtMost(items.size)
|
|
||||||
),
|
|
||||||
columns = columns,
|
|
||||||
highlightedItem = highlightedItem
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (after != null) {
|
|
||||||
item(key = "$key-after") {
|
|
||||||
PartialCardRow(
|
|
||||||
isFirst = items.isEmpty() && before == null,
|
|
||||||
isLast = true,
|
|
||||||
reverse = reverse
|
|
||||||
) {
|
|
||||||
after()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun GridRow(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
items: ImmutableList<SavableSearchable>,
|
|
||||||
columns: Int,
|
|
||||||
showLabels: Boolean = LocalGridSettings.current.showLabels,
|
|
||||||
highlightedItem: SavableSearchable?
|
|
||||||
) {
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = modifier
|
|
||||||
) {
|
|
||||||
for (item in items) {
|
|
||||||
GridItem(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.padding(4.dp),
|
|
||||||
item = item,
|
|
||||||
showLabels = showLabels,
|
|
||||||
highlight = item.key == highlightedItem?.key
|
|
||||||
)
|
|
||||||
}
|
|
||||||
for (i in 0 until columns - items.size) {
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun LazyListScope.ListResults(
|
|
||||||
items: ImmutableList<SavableSearchable>,
|
|
||||||
reverse: Boolean,
|
|
||||||
key: String,
|
|
||||||
before: (@Composable () -> Unit)? = null,
|
|
||||||
after: (@Composable () -> Unit)? = null,
|
|
||||||
highlightedItem: SavableSearchable?,
|
|
||||||
) {
|
|
||||||
if (before != null) {
|
|
||||||
item(key = "$key-before") {
|
|
||||||
PartialCardRow(
|
|
||||||
isFirst = true,
|
|
||||||
isLast = items.isEmpty() && after == null,
|
|
||||||
reverse = reverse
|
|
||||||
) {
|
|
||||||
before()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
items(
|
|
||||||
items.size,
|
|
||||||
key = {
|
|
||||||
"$key-${items[it].key}"
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
PartialCardRow(
|
|
||||||
isFirst = it == 0 && before == null,
|
|
||||||
isLast = it == items.lastIndex && after == null,
|
|
||||||
reverse = reverse
|
|
||||||
) {
|
|
||||||
ListRow(
|
|
||||||
modifier = Modifier.padding(
|
|
||||||
top = if (if (reverse) it == items.size - 1 else it == 0) 8.dp else 4.dp,
|
|
||||||
bottom = if (if (reverse) it == 0 else it == items.size - 1) 8.dp else 4.dp,
|
|
||||||
),
|
|
||||||
item = items[it],
|
|
||||||
highlight = items[it].key == highlightedItem?.key
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (after != null) {
|
|
||||||
item(key = "$key-after") {
|
|
||||||
PartialCardRow(
|
|
||||||
isFirst = items.isEmpty() && before == null,
|
|
||||||
isLast = true,
|
|
||||||
reverse = reverse
|
|
||||||
) {
|
|
||||||
after()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ListRow(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
item: SavableSearchable,
|
|
||||||
highlight: Boolean
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = modifier.padding(
|
|
||||||
start = 8.dp,
|
|
||||||
end = 8.dp,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
ListItem(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth(),
|
|
||||||
item = item,
|
|
||||||
highlight = highlight
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun LazyListScope.SingleResult(
|
fun LazyListScope.SingleResult(
|
||||||
highlight: Boolean = false,
|
highlight: Boolean = false,
|
||||||
@ -596,31 +344,15 @@ fun LazyListScope.SingleResult(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
enum class SearchCategory {
|
||||||
fun LazyItemScope.PartialCardRow(
|
Apps,
|
||||||
modifier: Modifier = Modifier,
|
Calculator,
|
||||||
isFirst: Boolean,
|
Calendar,
|
||||||
isLast: Boolean,
|
Contacts,
|
||||||
reverse: Boolean,
|
Files,
|
||||||
content: @Composable () -> Unit
|
UnitConverter,
|
||||||
) {
|
Wikipedia,
|
||||||
val isTop = isFirst && !reverse || isLast && reverse
|
Website,
|
||||||
val isBottom = isLast && !reverse || isFirst && reverse
|
Location,
|
||||||
Box(
|
Shortcut,
|
||||||
modifier = modifier
|
|
||||||
.clipToBounds()
|
|
||||||
) {
|
|
||||||
PartialLauncherCard(
|
|
||||||
modifier = Modifier.padding(
|
|
||||||
start = 8.dp,
|
|
||||||
end = 8.dp,
|
|
||||||
top = if (isTop) 4.dp else 0.dp,
|
|
||||||
bottom = if (isBottom) 4.dp else 0.dp,
|
|
||||||
),
|
|
||||||
isTop = isTop,
|
|
||||||
isBottom = isBottom,
|
|
||||||
) {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.search.apps
|
||||||
|
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.PrimaryTabRow
|
||||||
|
import androidx.compose.material3.Tab
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import de.mm20.launcher2.search.Application
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.ktx.animateCorners
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.grid.GridItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.grid.GridResults
|
||||||
|
import de.mm20.launcher2.ui.layout.BottomReversed
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalGridSettings
|
||||||
|
|
||||||
|
fun LazyListScope.AppResults(
|
||||||
|
apps: List<Application>,
|
||||||
|
showTabs: Boolean,
|
||||||
|
selectedTab: Int,
|
||||||
|
onSelectedTabChange: (Int) -> Unit,
|
||||||
|
highlightedItem: Application? = null,
|
||||||
|
columns: Int,
|
||||||
|
reverse: Boolean,
|
||||||
|
) {
|
||||||
|
|
||||||
|
GridResults(
|
||||||
|
key = "app",
|
||||||
|
items = apps,
|
||||||
|
itemContent = {
|
||||||
|
GridItem(
|
||||||
|
item = it,
|
||||||
|
showLabels = LocalGridSettings.current.showLabels,
|
||||||
|
highlight = it.key == highlightedItem?.key
|
||||||
|
)
|
||||||
|
},
|
||||||
|
before = if (showTabs) {
|
||||||
|
{
|
||||||
|
Column(
|
||||||
|
verticalArrangement = if (reverse) Arrangement.BottomReversed else Arrangement.Top,
|
||||||
|
) {
|
||||||
|
PrimaryTabRow(
|
||||||
|
selectedTabIndex = selectedTab,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(
|
||||||
|
MaterialTheme.shapes.medium.animateCorners(
|
||||||
|
topStart = !reverse,
|
||||||
|
topEnd = !reverse,
|
||||||
|
bottomEnd = reverse,
|
||||||
|
bottomStart = reverse,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
divider = {},
|
||||||
|
) {
|
||||||
|
Tab(
|
||||||
|
selected = selectedTab == 0,
|
||||||
|
onClick = { onSelectedTabChange(0) },
|
||||||
|
text = { Text(stringResource(R.string.apps_profile_main)) },
|
||||||
|
unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
Tab(
|
||||||
|
selected = selectedTab == 1,
|
||||||
|
onClick = { onSelectedTabChange(1) },
|
||||||
|
text = { Text(stringResource(R.string.apps_profile_work)) },
|
||||||
|
unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else null,
|
||||||
|
reverse = reverse,
|
||||||
|
columns = columns,
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.search.calculator
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import de.mm20.launcher2.search.data.Calculator
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListItemSurface
|
||||||
|
|
||||||
|
fun LazyListScope.CalculatorResults(
|
||||||
|
calculator: List<Calculator>,
|
||||||
|
reverse: Boolean,
|
||||||
|
) {
|
||||||
|
if (calculator.isNotEmpty()) {
|
||||||
|
item(key = "calculator") {
|
||||||
|
ListItemSurface(
|
||||||
|
isFirst = true,
|
||||||
|
isLast = true,
|
||||||
|
reverse = reverse,
|
||||||
|
) {
|
||||||
|
CalculatorItem(calculator = calculator.first())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.search.calendar
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import de.mm20.launcher2.search.CalendarEvent
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
|
||||||
|
|
||||||
|
fun LazyListScope.CalendarResults(
|
||||||
|
events: List<CalendarEvent>,
|
||||||
|
missingPermission: Boolean,
|
||||||
|
onPermissionRequest: () -> Unit,
|
||||||
|
onPermissionRequestRejected: () -> Unit,
|
||||||
|
selectedIndex: Int,
|
||||||
|
onSelect: (Int) -> Unit,
|
||||||
|
highlightedItem: CalendarEvent?,
|
||||||
|
reverse: Boolean,
|
||||||
|
) {
|
||||||
|
ListResults(
|
||||||
|
items = events,
|
||||||
|
key = "calendar",
|
||||||
|
reverse = reverse,
|
||||||
|
selectedIndex = selectedIndex,
|
||||||
|
itemContent = { calendar, showDetails, index ->
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
item = calendar,
|
||||||
|
showDetails = showDetails,
|
||||||
|
onShowDetails = { onSelect(if(it) index else -1) },
|
||||||
|
highlight = highlightedItem?.key == calendar.key
|
||||||
|
)
|
||||||
|
},
|
||||||
|
before = if (missingPermission) {
|
||||||
|
{
|
||||||
|
MissingPermissionBanner(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
text = stringResource(R.string.missing_permission_calendar_search),
|
||||||
|
onClick = onPermissionRequest,
|
||||||
|
secondaryAction = {
|
||||||
|
OutlinedButton(onClick = onPermissionRequestRejected) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.turn_off),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -80,6 +80,7 @@ fun GridItem(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
item: SavableSearchable,
|
item: SavableSearchable,
|
||||||
showLabels: Boolean = true,
|
showLabels: Boolean = true,
|
||||||
|
labelMaxLines: Int = 1,
|
||||||
highlight: Boolean = false
|
highlight: Boolean = false
|
||||||
) {
|
) {
|
||||||
val viewModel: SearchableItemVM = listItemViewModel(key = "search-${item.key}")
|
val viewModel: SearchableItemVM = listItemViewModel(key = "search-${item.key}")
|
||||||
@ -181,8 +182,9 @@ fun GridItem(
|
|||||||
text = item.labelOverride ?: item.label,
|
text = item.labelOverride ?: item.label,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
maxLines = 1,
|
maxLines = labelMaxLines,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +225,7 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.systemBarsPadding()
|
.systemBarsPadding()
|
||||||
.imePadding()
|
.imePadding()
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 8.dp)
|
||||||
.then(
|
.then(
|
||||||
if (show.targetState) {
|
if (show.targetState) {
|
||||||
Modifier.pointerInput(Unit) {
|
Modifier.pointerInput(Unit) {
|
||||||
@ -242,7 +244,7 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.placeOverlay(
|
.placeOverlay(
|
||||||
origin.translate(
|
origin.translate(
|
||||||
-16.dp.toPixels(),
|
-8.dp.toPixels(),
|
||||||
-WindowInsets.systemBars.union(WindowInsets.ime)
|
-WindowInsets.systemBars.union(WindowInsets.ime)
|
||||||
.getTop(LocalDensity.current).toFloat()
|
.getTop(LocalDensity.current).toFloat()
|
||||||
),
|
),
|
||||||
|
|||||||
@ -0,0 +1,130 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.search.common.grid
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
|
import de.mm20.launcher2.ui.ktx.withCorners
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
||||||
|
import kotlin.math.ceil
|
||||||
|
|
||||||
|
fun <T : SavableSearchable> LazyListScope.GridResults(
|
||||||
|
key: String,
|
||||||
|
items: List<T>,
|
||||||
|
itemContent: @Composable (T) -> Unit,
|
||||||
|
before: @Composable (() -> Unit)? = null,
|
||||||
|
after: @Composable (() -> Unit)? = null,
|
||||||
|
columns: Int,
|
||||||
|
reverse: Boolean = false,
|
||||||
|
) {
|
||||||
|
if (before != null) {
|
||||||
|
item(
|
||||||
|
key = "$key-before",
|
||||||
|
) {
|
||||||
|
val isTop = !reverse || items.isEmpty() && after == null
|
||||||
|
val isBottom = reverse || items.isEmpty() && after == null
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surface.copy(alpha = LocalCardStyle.current.opacity),
|
||||||
|
MaterialTheme.shapes.medium.withCorners(
|
||||||
|
topStart = isTop,
|
||||||
|
topEnd = isTop,
|
||||||
|
bottomEnd = isBottom,
|
||||||
|
bottomStart = isBottom,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
before()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val rows = ceil(items.size / columns.toFloat()).toInt()
|
||||||
|
items(
|
||||||
|
rows,
|
||||||
|
key = {
|
||||||
|
"$key-$it"
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
val isFirst = it == 0 && before == null
|
||||||
|
val isLast = it == rows - 1 && after == null
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(
|
||||||
|
top = if (reverse && isLast) 8.dp else 0.dp,
|
||||||
|
bottom = if (!reverse && isLast) 8.dp else 0.dp,
|
||||||
|
)
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surface.copy(alpha = LocalCardStyle.current.opacity),
|
||||||
|
MaterialTheme.shapes.medium.withCorners(
|
||||||
|
topStart = isFirst && !reverse || isLast && reverse,
|
||||||
|
topEnd = isFirst && !reverse || isLast && reverse,
|
||||||
|
bottomEnd = isLast && !reverse || isFirst && reverse,
|
||||||
|
bottomStart = isLast && !reverse || isFirst && reverse,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.padding(
|
||||||
|
top = if (it == 0) 8.dp else 0.dp,
|
||||||
|
bottom = if (it == rows - 1) 8.dp else 0.dp,
|
||||||
|
start = 4.dp,
|
||||||
|
end = 4.dp,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
for (i in 0 until columns) {
|
||||||
|
val item = items.getOrNull(it * columns + i)
|
||||||
|
if (item != null) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(4.dp)
|
||||||
|
) {
|
||||||
|
itemContent(item)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (after != null) {
|
||||||
|
item(
|
||||||
|
key = "$key-after",
|
||||||
|
) {
|
||||||
|
val isTop = reverse || items.isEmpty() && before == null
|
||||||
|
val isBottom = !reverse || items.isEmpty() && before == null
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
top = if (reverse) 8.dp else 0.dp,
|
||||||
|
bottom = if (!reverse) 8.dp else 0.dp,
|
||||||
|
)
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surface.copy(alpha = LocalCardStyle.current.opacity),
|
||||||
|
MaterialTheme.shapes.medium.withCorners(
|
||||||
|
topStart = isTop,
|
||||||
|
topEnd = isTop,
|
||||||
|
bottomEnd = isBottom,
|
||||||
|
bottomStart = isBottom,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
after()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,21 @@
|
|||||||
package de.mm20.launcher2.ui.launcher.search.common.list
|
package de.mm20.launcher2.ui.launcher.search.common.list
|
||||||
|
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Rect
|
import androidx.compose.ui.geometry.Rect
|
||||||
import androidx.compose.ui.layout.boundsInWindow
|
import androidx.compose.ui.layout.boundsInWindow
|
||||||
@ -10,12 +23,13 @@ import androidx.compose.ui.layout.onGloballyPositioned
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import de.mm20.launcher2.search.AppShortcut
|
import de.mm20.launcher2.search.AppShortcut
|
||||||
|
import de.mm20.launcher2.search.Article
|
||||||
import de.mm20.launcher2.search.CalendarEvent
|
import de.mm20.launcher2.search.CalendarEvent
|
||||||
import de.mm20.launcher2.search.Contact
|
import de.mm20.launcher2.search.Contact
|
||||||
import de.mm20.launcher2.search.File
|
import de.mm20.launcher2.search.File
|
||||||
import de.mm20.launcher2.search.Location
|
import de.mm20.launcher2.search.Location
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.ui.component.InnerCard
|
import de.mm20.launcher2.search.Website
|
||||||
import de.mm20.launcher2.ui.ktx.toPixels
|
import de.mm20.launcher2.ui.ktx.toPixels
|
||||||
import de.mm20.launcher2.ui.launcher.search.calendar.CalendarItem
|
import de.mm20.launcher2.ui.launcher.search.calendar.CalendarItem
|
||||||
import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM
|
import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM
|
||||||
@ -24,15 +38,18 @@ import de.mm20.launcher2.ui.launcher.search.files.FileItem
|
|||||||
import de.mm20.launcher2.ui.launcher.search.listItemViewModel
|
import de.mm20.launcher2.ui.launcher.search.listItemViewModel
|
||||||
import de.mm20.launcher2.ui.launcher.search.location.LocationItem
|
import de.mm20.launcher2.ui.launcher.search.location.LocationItem
|
||||||
import de.mm20.launcher2.ui.launcher.search.shortcut.AppShortcutItem
|
import de.mm20.launcher2.ui.launcher.search.shortcut.AppShortcutItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.website.WebsiteItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.wikipedia.ArticleItem
|
||||||
import de.mm20.launcher2.ui.locals.LocalGridSettings
|
import de.mm20.launcher2.ui.locals.LocalGridSettings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ListItem(
|
fun ListItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
item: SavableSearchable,
|
item: SavableSearchable,
|
||||||
highlight: Boolean = false
|
highlight: Boolean = false,
|
||||||
|
showDetails: Boolean,
|
||||||
|
onShowDetails: (Boolean) -> Unit
|
||||||
) {
|
) {
|
||||||
var showDetails by remember { mutableStateOf(false) }
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val viewModel: SearchableItemVM = listItemViewModel(key = "search-${item.key}")
|
val viewModel: SearchableItemVM = listItemViewModel(key = "search-${item.key}")
|
||||||
@ -46,99 +63,142 @@ fun ListItem(
|
|||||||
if (showDetails) viewModel.requestUpdatedSearchable(context)
|
if (showDetails) viewModel.requestUpdatedSearchable(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val background by animateColorAsState(
|
||||||
|
if (highlight) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface.copy(
|
||||||
|
alpha = 0f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
val item = viewModel.searchable.collectAsState().value ?: item
|
val item = viewModel.searchable.collectAsState().value ?: item
|
||||||
|
|
||||||
var bounds by remember { mutableStateOf(Rect.Zero) }
|
var bounds by remember { mutableStateOf(Rect.Zero) }
|
||||||
InnerCard(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
.background(background)
|
||||||
.onGloballyPositioned {
|
.onGloballyPositioned {
|
||||||
bounds = it.boundsInWindow()
|
bounds = it.boundsInWindow()
|
||||||
},
|
},
|
||||||
highlight = highlight,
|
|
||||||
raised = showDetails
|
|
||||||
) {
|
) {
|
||||||
when (item) {
|
CompositionLocalProvider(
|
||||||
is Contact -> {
|
LocalContentColor provides MaterialTheme.colorScheme.onSurface
|
||||||
ContactItem(
|
) {
|
||||||
modifier = Modifier
|
when (item) {
|
||||||
.fillMaxWidth()
|
is Contact -> {
|
||||||
.combinedClickable(
|
ContactItem(
|
||||||
enabled = !showDetails,
|
modifier = Modifier
|
||||||
onClick = { showDetails = true },
|
.fillMaxWidth()
|
||||||
onLongClick = { showDetails = true }
|
.combinedClickable(
|
||||||
),
|
enabled = !showDetails,
|
||||||
contact = item,
|
onClick = { onShowDetails(true) },
|
||||||
showDetails = showDetails,
|
onLongClick = { onShowDetails(true) }
|
||||||
onBack = { showDetails = false }
|
),
|
||||||
)
|
contact = item,
|
||||||
|
showDetails = showDetails,
|
||||||
|
onBack = { onShowDetails(false) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is File -> {
|
||||||
|
FileItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.combinedClickable(
|
||||||
|
enabled = !showDetails,
|
||||||
|
onClick = {
|
||||||
|
if (!viewModel.launch(context, bounds)) {
|
||||||
|
onShowDetails(true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClick = { onShowDetails(true) }
|
||||||
|
),
|
||||||
|
file = item,
|
||||||
|
showDetails = showDetails,
|
||||||
|
onBack = { onShowDetails(false) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is CalendarEvent -> {
|
||||||
|
CalendarItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.combinedClickable(
|
||||||
|
enabled = !showDetails,
|
||||||
|
onClick = { onShowDetails(true) },
|
||||||
|
onLongClick = { onShowDetails(true) }
|
||||||
|
)
|
||||||
|
.padding(top = 4.dp, end = 4.dp, bottom = 4.dp),
|
||||||
|
calendar = item,
|
||||||
|
showDetails = showDetails,
|
||||||
|
onBack = { onShowDetails(false) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Location -> {
|
||||||
|
LocationItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.combinedClickable(
|
||||||
|
enabled = !showDetails,
|
||||||
|
onClick = { onShowDetails(true) },
|
||||||
|
onLongClick = { onShowDetails(true) }),
|
||||||
|
location = item,
|
||||||
|
showDetails = showDetails,
|
||||||
|
onBack = { onShowDetails(false) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is AppShortcut -> {
|
||||||
|
AppShortcutItem(
|
||||||
|
shortcut = item,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.combinedClickable(
|
||||||
|
enabled = !showDetails,
|
||||||
|
onClick = {
|
||||||
|
if (!viewModel.launch(context, bounds)) {
|
||||||
|
onShowDetails(true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClick = { onShowDetails(true) }
|
||||||
|
),
|
||||||
|
showDetails = showDetails,
|
||||||
|
onBack = { onShowDetails(false) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Article -> {
|
||||||
|
ArticleItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.combinedClickable(
|
||||||
|
enabled = !showDetails,
|
||||||
|
onClick = {
|
||||||
|
if (!viewModel.launch(context, bounds)) {
|
||||||
|
onShowDetails(true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClick = { onShowDetails(true) }),
|
||||||
|
article = item,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Website -> {
|
||||||
|
WebsiteItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.combinedClickable(
|
||||||
|
enabled = !showDetails,
|
||||||
|
onClick = {
|
||||||
|
if (!viewModel.launch(context, bounds)) {
|
||||||
|
onShowDetails(true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClick = { onShowDetails(true) }),
|
||||||
|
website = item,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is File -> {
|
|
||||||
FileItem(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.combinedClickable(
|
|
||||||
enabled = !showDetails,
|
|
||||||
onClick = {
|
|
||||||
if (!viewModel.launch(context, bounds)) {
|
|
||||||
showDetails = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLongClick = { showDetails = true }
|
|
||||||
),
|
|
||||||
file = item,
|
|
||||||
showDetails = showDetails,
|
|
||||||
onBack = { showDetails = false }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is CalendarEvent -> {
|
|
||||||
CalendarItem(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.combinedClickable(
|
|
||||||
enabled = !showDetails,
|
|
||||||
onClick = { showDetails = true },
|
|
||||||
onLongClick = { showDetails = true }
|
|
||||||
),
|
|
||||||
calendar = item,
|
|
||||||
showDetails = showDetails,
|
|
||||||
onBack = { showDetails = false }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is Location -> {
|
|
||||||
LocationItem(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.combinedClickable(
|
|
||||||
enabled = !showDetails,
|
|
||||||
onClick = { showDetails = true },
|
|
||||||
onLongClick = { showDetails = true }),
|
|
||||||
location = item,
|
|
||||||
showDetails = showDetails,
|
|
||||||
onBack = { showDetails = false }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is AppShortcut -> {
|
|
||||||
AppShortcutItem(
|
|
||||||
shortcut = item,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.combinedClickable(
|
|
||||||
enabled = !showDetails,
|
|
||||||
onClick = {
|
|
||||||
if (!viewModel.launch(context, bounds)) {
|
|
||||||
showDetails = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLongClick = { showDetails = true }
|
|
||||||
),
|
|
||||||
showDetails = showDetails,
|
|
||||||
onBack = { showDetails = false }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,159 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.search.common.list
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.core.animateDp
|
||||||
|
import androidx.compose.animation.core.animateFloat
|
||||||
|
import androidx.compose.animation.core.updateTransition
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
|
import de.mm20.launcher2.ui.ktx.animateCorners
|
||||||
|
import de.mm20.launcher2.ui.layout.BottomReversed
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
||||||
|
|
||||||
|
fun <T : SavableSearchable> LazyListScope.ListResults(
|
||||||
|
key: String,
|
||||||
|
items: List<T>,
|
||||||
|
itemContent: @Composable LazyItemScope.(T, Boolean, Int) -> Unit,
|
||||||
|
before: @Composable (LazyItemScope.() -> Unit)? = null,
|
||||||
|
after: @Composable (LazyItemScope.() -> Unit)? = null,
|
||||||
|
reverse: Boolean = false,
|
||||||
|
selectedIndex: Int = -1,
|
||||||
|
) {
|
||||||
|
if (before != null) {
|
||||||
|
item(
|
||||||
|
key = "$key-before",
|
||||||
|
) {
|
||||||
|
ListItemSurface(
|
||||||
|
isFirst = true,
|
||||||
|
isLast = after == null && items.isEmpty(),
|
||||||
|
reverse = reverse,
|
||||||
|
isBeforeExpanded = selectedIndex == 0,
|
||||||
|
) {
|
||||||
|
before()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val rows = items.size
|
||||||
|
items(
|
||||||
|
items.size,
|
||||||
|
key = {
|
||||||
|
"$key-${items[it].key}"
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
val item = items[it]
|
||||||
|
val showDetails = it == selectedIndex
|
||||||
|
|
||||||
|
ListItemSurface(
|
||||||
|
isFirst = it == 0 && before == null,
|
||||||
|
isLast = it == rows - 1 && after == null,
|
||||||
|
reverse = reverse,
|
||||||
|
isExpanded = showDetails,
|
||||||
|
isBeforeExpanded = selectedIndex - 1 == it,
|
||||||
|
isAfterExpanded = selectedIndex + 1 == it,
|
||||||
|
) {
|
||||||
|
itemContent(item, showDetails, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (after != null) {
|
||||||
|
item(
|
||||||
|
key = "$key-after",
|
||||||
|
) {
|
||||||
|
ListItemSurface(
|
||||||
|
isFirst = before == null && items.isEmpty(),
|
||||||
|
isLast = true,
|
||||||
|
reverse = reverse,
|
||||||
|
isAfterExpanded = selectedIndex == items.lastIndex,
|
||||||
|
) {
|
||||||
|
after()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LazyItemScope.ListItemSurface(
|
||||||
|
isFirst: Boolean = false,
|
||||||
|
isLast: Boolean = false,
|
||||||
|
reverse: Boolean = false,
|
||||||
|
isExpanded: Boolean = false,
|
||||||
|
isBeforeExpanded: Boolean = false,
|
||||||
|
isAfterExpanded: Boolean = false,
|
||||||
|
content: @Composable ColumnScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
val transition = updateTransition(isExpanded)
|
||||||
|
val elevation by transition.animateDp {
|
||||||
|
if (it) 2.dp else 0.dp
|
||||||
|
}
|
||||||
|
val backgroundAlpha by transition.animateFloat {
|
||||||
|
if (it) 1f else LocalCardStyle.current.opacity
|
||||||
|
}
|
||||||
|
|
||||||
|
val padding by transition.animateDp {
|
||||||
|
if (it) 8.dp else 0.dp
|
||||||
|
}
|
||||||
|
|
||||||
|
val modifier = if (reverse) {
|
||||||
|
Modifier
|
||||||
|
.animateItem()
|
||||||
|
.padding(
|
||||||
|
bottom = if (!isFirst) padding else 0.dp,
|
||||||
|
top = if (!isLast) padding else 8.dp
|
||||||
|
)
|
||||||
|
.shadow(
|
||||||
|
elevation = elevation,
|
||||||
|
MaterialTheme.shapes.medium.animateCorners(
|
||||||
|
bottomStart = isFirst || isExpanded || isAfterExpanded,
|
||||||
|
bottomEnd = isFirst || isExpanded || isAfterExpanded,
|
||||||
|
topEnd = isLast || isExpanded || isBeforeExpanded,
|
||||||
|
topStart = isLast || isExpanded || isBeforeExpanded,
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
.padding(
|
||||||
|
top = if (!isFirst) padding else 0.dp,
|
||||||
|
bottom = if (!isLast) padding else 8.dp
|
||||||
|
)
|
||||||
|
.shadow(
|
||||||
|
elevation = elevation,
|
||||||
|
MaterialTheme.shapes.medium.animateCorners(
|
||||||
|
topStart = isFirst || isExpanded || isAfterExpanded,
|
||||||
|
topEnd = isFirst || isExpanded || isAfterExpanded,
|
||||||
|
bottomEnd = isLast || isExpanded || isBeforeExpanded,
|
||||||
|
bottomStart = isLast || isExpanded || isBeforeExpanded,
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(MaterialTheme.colorScheme.surface.copy(backgroundAlpha)),
|
||||||
|
verticalArrangement = if (reverse) Arrangement.BottomReversed else Arrangement.Top
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(!isFirst && !isExpanded && !isAfterExpanded) {
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
|
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,21 @@
|
|||||||
package de.mm20.launcher2.ui.launcher.search.common.list
|
package de.mm20.launcher2.ui.launcher.search.common.list
|
||||||
|
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.key
|
import androidx.compose.runtime.key
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.ui.layout.BottomReversed
|
import de.mm20.launcher2.ui.layout.BottomReversed
|
||||||
@ -18,18 +27,25 @@ fun SearchResultList(
|
|||||||
reverse: Boolean = false,
|
reverse: Boolean = false,
|
||||||
highlightedItem: SavableSearchable? = null
|
highlightedItem: SavableSearchable? = null
|
||||||
) {
|
) {
|
||||||
|
var selectedIndex by remember { mutableIntStateOf(-1) }
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier,
|
modifier = modifier
|
||||||
|
.clip(MaterialTheme.shapes.small)
|
||||||
|
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, MaterialTheme.shapes.small),
|
||||||
verticalArrangement = if (reverse) Arrangement.BottomReversed else Arrangement.Top
|
verticalArrangement = if (reverse) Arrangement.BottomReversed else Arrangement.Top
|
||||||
) {
|
) {
|
||||||
for (item in items) {
|
for ((i, item) in items.withIndex()) {
|
||||||
key(item.key) {
|
key(item.key) {
|
||||||
|
if (i != 0) {
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
ListItem(
|
ListItem(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
.padding(4.dp),
|
|
||||||
item = item,
|
item = item,
|
||||||
highlight = item.key == highlightedItem?.key
|
highlight = item.key == highlightedItem?.key,
|
||||||
|
showDetails = selectedIndex == i,
|
||||||
|
onShowDetails = { selectedIndex = if (it) i else -1}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,9 +104,7 @@ fun ContactItem(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
val icon by viewModel.icon.collectAsStateWithLifecycle()
|
val icon by viewModel.icon.collectAsStateWithLifecycle()
|
||||||
val padding by transition.animateDp(label = "iconPadding") {
|
val padding = 16.dp
|
||||||
if (it) 16.dp else 8.dp
|
|
||||||
}
|
|
||||||
ShapedLauncherIcon(
|
ShapedLauncherIcon(
|
||||||
size = 48.dp,
|
size = 48.dp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@ -0,0 +1,59 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.search.contacts
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import de.mm20.launcher2.search.Contact
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
|
||||||
|
|
||||||
|
fun LazyListScope.ContactResults(
|
||||||
|
contacts: List<Contact>,
|
||||||
|
missingPermission: Boolean,
|
||||||
|
onPermissionRequest: () -> Unit,
|
||||||
|
onPermissionRequestRejected: () -> Unit,
|
||||||
|
selectedIndex: Int,
|
||||||
|
onSelect: (Int) -> Unit,
|
||||||
|
highlightedItem: Contact?,
|
||||||
|
reverse: Boolean,
|
||||||
|
) {
|
||||||
|
ListResults(
|
||||||
|
items = contacts,
|
||||||
|
key = "contact",
|
||||||
|
reverse = reverse,
|
||||||
|
selectedIndex = selectedIndex,
|
||||||
|
itemContent = { contact, showDetails, index ->
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
item = contact,
|
||||||
|
showDetails = showDetails,
|
||||||
|
onShowDetails = { onSelect(if(it) index else -1) },
|
||||||
|
highlight = highlightedItem?.key == contact.key
|
||||||
|
)
|
||||||
|
},
|
||||||
|
before = if (missingPermission) {
|
||||||
|
{
|
||||||
|
MissingPermissionBanner(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
text = stringResource(R.string.missing_permission_contact_search),
|
||||||
|
onClick = onPermissionRequest,
|
||||||
|
secondaryAction = {
|
||||||
|
OutlinedButton(onClick = onPermissionRequestRejected) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.turn_off),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.search.favorites
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyGridScope
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Star
|
||||||
|
import androidx.compose.material.icons.rounded.Tag
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
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.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
|
import de.mm20.launcher2.search.data.Tag
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.common.FavoritesTagSelector
|
||||||
|
import de.mm20.launcher2.ui.component.Banner
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
|
||||||
|
import de.mm20.launcher2.ui.launcher.widgets.favorites.FavoritesWidgetVM
|
||||||
|
|
||||||
|
fun LazyListScope.SearchFavorites(
|
||||||
|
favorites: List<SavableSearchable>,
|
||||||
|
pinnedTags: List<Tag>,
|
||||||
|
selectedTag: String?,
|
||||||
|
tagsExpanded: Boolean,
|
||||||
|
onExpandTags: (Boolean) -> Unit,
|
||||||
|
onSelectTag: (String?) -> Unit,
|
||||||
|
editButton: Boolean,
|
||||||
|
reverse: Boolean,
|
||||||
|
) {
|
||||||
|
item(
|
||||||
|
key = "favorites",
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
top = if (reverse) 8.dp else 0.dp,
|
||||||
|
bottom = if (reverse) 0.dp else 8.dp,
|
||||||
|
)
|
||||||
|
.background(MaterialTheme.colorScheme.surface, MaterialTheme.shapes.medium)
|
||||||
|
) {
|
||||||
|
if (favorites.isNotEmpty()) {
|
||||||
|
SearchResultGrid(favorites)
|
||||||
|
} else {
|
||||||
|
Banner(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
text = stringResource(
|
||||||
|
if (selectedTag == null) R.string.favorites_empty else R.string.favorites_empty_tag
|
||||||
|
),
|
||||||
|
icon = if (selectedTag == null) Icons.Rounded.Star else Icons.Rounded.Tag,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (pinnedTags.isNotEmpty() || editButton) {
|
||||||
|
FavoritesTagSelector(
|
||||||
|
tags = pinnedTags,
|
||||||
|
selectedTag = selectedTag,
|
||||||
|
editButton = editButton,
|
||||||
|
reverse = false,
|
||||||
|
onSelectTag = onSelectTag,
|
||||||
|
scrollState = rememberScrollState(),
|
||||||
|
expanded = tagsExpanded,
|
||||||
|
onExpand = onExpandTags,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.search.files
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import de.mm20.launcher2.search.File
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
|
||||||
|
|
||||||
|
fun LazyListScope.FileResults(
|
||||||
|
files: List<File>,
|
||||||
|
missingPermission: Boolean,
|
||||||
|
onPermissionRequest: () -> Unit,
|
||||||
|
onPermissionRequestRejected: () -> Unit,
|
||||||
|
selectedIndex: Int,
|
||||||
|
onSelect: (Int) -> Unit,
|
||||||
|
highlightedItem: File? = null,
|
||||||
|
reverse: Boolean,
|
||||||
|
) {
|
||||||
|
ListResults(
|
||||||
|
items = files,
|
||||||
|
key = "file",
|
||||||
|
reverse = reverse,
|
||||||
|
selectedIndex = selectedIndex,
|
||||||
|
itemContent = { file, showDetails, index ->
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
item = file,
|
||||||
|
showDetails = showDetails,
|
||||||
|
onShowDetails = { onSelect(if (it) index else -1) },
|
||||||
|
highlight = highlightedItem?.key == file.key
|
||||||
|
)
|
||||||
|
},
|
||||||
|
before = if (missingPermission) {
|
||||||
|
{
|
||||||
|
MissingPermissionBanner(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
text = stringResource(R.string.missing_permission_files_search),
|
||||||
|
onClick = onPermissionRequest,
|
||||||
|
secondaryAction = {
|
||||||
|
OutlinedButton(onClick = onPermissionRequestRejected) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.turn_off),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -156,7 +156,7 @@ fun LocationItem(
|
|||||||
enter = slideIn { IntOffset(-it.width, 0) } + fadeIn(),
|
enter = slideIn { IntOffset(-it.width, 0) } + fadeIn(),
|
||||||
exit = slideOut { IntOffset(-it.width, 0) } + fadeOut(),
|
exit = slideOut { IntOffset(-it.width, 0) } + fadeOut(),
|
||||||
)
|
)
|
||||||
.padding(8.dp),
|
.padding(12.dp),
|
||||||
size = 48.dp,
|
size = 48.dp,
|
||||||
icon = { icon },
|
icon = { icon },
|
||||||
badge = { badge },
|
badge = { badge },
|
||||||
@ -201,7 +201,7 @@ fun LocationItem(
|
|||||||
}
|
}
|
||||||
Compass(
|
Compass(
|
||||||
targetHeading = targetHeading,
|
targetHeading = targetHeading,
|
||||||
modifier = Modifier.padding(end = 8.dp) then
|
modifier = Modifier.padding(end = 12.dp) then
|
||||||
if (!showMap) {
|
if (!showMap) {
|
||||||
Modifier.sharedBounds(
|
Modifier.sharedBounds(
|
||||||
rememberSharedContentState("compass"),
|
rememberSharedContentState("compass"),
|
||||||
|
|||||||
@ -0,0 +1,59 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.search.location
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import de.mm20.launcher2.search.Location
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
|
||||||
|
|
||||||
|
fun LazyListScope.LocationResults(
|
||||||
|
locations: List<Location>,
|
||||||
|
missingPermission: Boolean,
|
||||||
|
onPermissionRequest: () -> Unit,
|
||||||
|
onPermissionRequestRejected: () -> Unit,
|
||||||
|
selectedIndex: Int,
|
||||||
|
onSelect: (Int) -> Unit,
|
||||||
|
highlightedItem: Location?,
|
||||||
|
reverse: Boolean,
|
||||||
|
) {
|
||||||
|
ListResults(
|
||||||
|
items = locations,
|
||||||
|
key = "location",
|
||||||
|
reverse = reverse,
|
||||||
|
selectedIndex = selectedIndex,
|
||||||
|
itemContent = { location, showDetails, index ->
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
item = location,
|
||||||
|
showDetails = showDetails,
|
||||||
|
onShowDetails = { onSelect(if(it) index else -1) },
|
||||||
|
highlight = highlightedItem?.key == location.key
|
||||||
|
)
|
||||||
|
},
|
||||||
|
before = if (missingPermission) {
|
||||||
|
{
|
||||||
|
MissingPermissionBanner(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
text = stringResource(R.string.missing_permission_location_search),
|
||||||
|
onClick = onPermissionRequest,
|
||||||
|
secondaryAction = {
|
||||||
|
OutlinedButton(onClick = onPermissionRequestRejected) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.turn_off),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.search.shortcut
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import de.mm20.launcher2.search.AppShortcut
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
|
||||||
|
|
||||||
|
fun LazyListScope.ShortcutResults(
|
||||||
|
shortcuts: List<AppShortcut>,
|
||||||
|
missingPermission: Boolean,
|
||||||
|
onPermissionRequest: () -> Unit,
|
||||||
|
onPermissionRequestRejected: () -> Unit,
|
||||||
|
selectedIndex: Int,
|
||||||
|
onSelect: (Int) -> Unit,
|
||||||
|
highlightedItem: AppShortcut?,
|
||||||
|
reverse: Boolean,
|
||||||
|
) {
|
||||||
|
ListResults(
|
||||||
|
items = shortcuts,
|
||||||
|
key = "shortcut",
|
||||||
|
reverse = reverse,
|
||||||
|
selectedIndex = selectedIndex,
|
||||||
|
itemContent = { shortcut, showDetails, index ->
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
item = shortcut,
|
||||||
|
showDetails = showDetails,
|
||||||
|
onShowDetails = { onSelect(if(it) index else -1) },
|
||||||
|
highlight = highlightedItem?.key == shortcut.key
|
||||||
|
)
|
||||||
|
},
|
||||||
|
before = if (missingPermission) {
|
||||||
|
{
|
||||||
|
MissingPermissionBanner(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
text = stringResource(R.string.missing_permission_appshortcuts_search),
|
||||||
|
onClick = onPermissionRequest,
|
||||||
|
secondaryAction = {
|
||||||
|
OutlinedButton(onClick = onPermissionRequestRejected) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.turn_off),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -140,21 +140,3 @@ fun UnitConverterItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDimensionIcon(dimension: Dimension): ImageVector {
|
|
||||||
return when (dimension) {
|
|
||||||
Dimension.Mass -> Icons.Rounded.FitnessCenter
|
|
||||||
Dimension.Length -> Icons.Rounded.Straighten
|
|
||||||
Dimension.Velocity -> Icons.Rounded.Speed
|
|
||||||
Dimension.Volume -> TODO()
|
|
||||||
Dimension.Area -> Icons.Rounded.SquareFoot
|
|
||||||
Dimension.Currency -> Icons.Rounded.Toll
|
|
||||||
Dimension.Data -> Icons.Rounded.Storage
|
|
||||||
Dimension.Bitrate -> TODO()
|
|
||||||
Dimension.Pressure -> TODO()
|
|
||||||
Dimension.Energy -> Icons.Rounded.Bolt
|
|
||||||
Dimension.Frequency -> TODO()
|
|
||||||
Dimension.Temperature -> Icons.Rounded.Thermostat
|
|
||||||
Dimension.Time -> Icons.Rounded.Schedule
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,252 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.search.unitconverter
|
||||||
|
|
||||||
|
import android.icu.text.DateFormat
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.rounded.ArrowForward
|
||||||
|
import androidx.compose.material.icons.rounded.Bolt
|
||||||
|
import androidx.compose.material.icons.rounded.FitnessCenter
|
||||||
|
import androidx.compose.material.icons.rounded.Schedule
|
||||||
|
import androidx.compose.material.icons.rounded.Speed
|
||||||
|
import androidx.compose.material.icons.rounded.SquareFoot
|
||||||
|
import androidx.compose.material.icons.rounded.Storage
|
||||||
|
import androidx.compose.material.icons.rounded.Straighten
|
||||||
|
import androidx.compose.material.icons.rounded.Thermostat
|
||||||
|
import androidx.compose.material.icons.rounded.Toll
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
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.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import de.mm20.launcher2.search.data.CurrencyUnitConverter
|
||||||
|
import de.mm20.launcher2.search.data.UnitConverter
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.ktx.TextButtonWithTrailingIconContentPadding
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListItemSurface
|
||||||
|
import de.mm20.launcher2.unitconverter.Dimension
|
||||||
|
import java.util.Date
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
fun LazyListScope.UnitConverterResults(
|
||||||
|
converters: List<UnitConverter>,
|
||||||
|
truncate: Boolean,
|
||||||
|
onShowAll: () -> Unit,
|
||||||
|
reverse: Boolean,
|
||||||
|
) {
|
||||||
|
if (converters.isNotEmpty()) {
|
||||||
|
val converter = converters.first()
|
||||||
|
item(
|
||||||
|
key = "converter-header",
|
||||||
|
) {
|
||||||
|
ListItemSurface(
|
||||||
|
isFirst = true,
|
||||||
|
reverse = reverse,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = converter.inputValue.let { "${it.formattedValue} ${it.formattedName}" },
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
softWrap = false
|
||||||
|
)
|
||||||
|
if (converter is CurrencyUnitConverter) {
|
||||||
|
var showDisclaimer by remember { mutableStateOf(false) }
|
||||||
|
val df = DateFormat.getDateInstance(DateFormat.SHORT)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 2.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "${df.format(Date(converter.updateTimestamp))} • ",
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.disclaimer),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
showDisclaimer = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (showDisclaimer) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showDisclaimer = false },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { showDisclaimer = false }) {
|
||||||
|
Text(text = stringResource(id = R.string.close))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = { Text(stringResource(id = R.string.disclaimer)) },
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
id = R.string.disclaimer_currency_converter,
|
||||||
|
df.format(Date(converter.updateTimestamp))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Icon(
|
||||||
|
imageVector = getDimensionIcon(converter.dimension),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.secondary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
top = 20.dp,
|
||||||
|
bottom = 20.dp,
|
||||||
|
start = 16.dp,
|
||||||
|
end = 18.dp
|
||||||
|
)
|
||||||
|
.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val count = if (truncate) min(5, converter.values.size) else converter.values.size
|
||||||
|
items(
|
||||||
|
count,
|
||||||
|
key = { "converter-${converter.values[it].symbol}" }
|
||||||
|
) {
|
||||||
|
val value = converter.values[it]
|
||||||
|
ListItemSurface(
|
||||||
|
isLast = it == converter.values.lastIndex,
|
||||||
|
reverse = reverse,
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
clipboardManager.setText(buildAnnotatedString {
|
||||||
|
append(value.value.toString())
|
||||||
|
append(" ")
|
||||||
|
append(value.symbol)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.padding(
|
||||||
|
start = 16.dp,
|
||||||
|
end = 12.dp,
|
||||||
|
top = 12.dp,
|
||||||
|
bottom = 12.dp,
|
||||||
|
),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.widthIn(min = 48.dp),
|
||||||
|
text = value.formattedValue,
|
||||||
|
style = MaterialTheme.typography.labelLarge
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
text = value.formattedName,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.secondaryContainer,
|
||||||
|
MaterialTheme.shapes.extraSmall,
|
||||||
|
)
|
||||||
|
.height(36.dp)
|
||||||
|
.widthIn(min = 36.dp)
|
||||||
|
.padding(4.dp),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
text = value.symbol,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (truncate && converter.values.size > 5) {
|
||||||
|
item(
|
||||||
|
key = "converter-footer"
|
||||||
|
) {
|
||||||
|
ListItemSurface(
|
||||||
|
isLast = true,
|
||||||
|
reverse = reverse,
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.End)
|
||||||
|
.padding(4.dp),
|
||||||
|
onClick = onShowAll,
|
||||||
|
contentPadding = ButtonDefaults.TextButtonWithTrailingIconContentPadding,
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.unit_converter_show_all))
|
||||||
|
Icon(
|
||||||
|
Icons.AutoMirrored.Rounded.ArrowForward,
|
||||||
|
null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = ButtonDefaults.IconSpacing)
|
||||||
|
.size(ButtonDefaults.IconSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDimensionIcon(dimension: Dimension): ImageVector {
|
||||||
|
return when (dimension) {
|
||||||
|
Dimension.Mass -> Icons.Rounded.FitnessCenter
|
||||||
|
Dimension.Length -> Icons.Rounded.Straighten
|
||||||
|
Dimension.Velocity -> Icons.Rounded.Speed
|
||||||
|
Dimension.Volume -> TODO()
|
||||||
|
Dimension.Area -> Icons.Rounded.SquareFoot
|
||||||
|
Dimension.Currency -> Icons.Rounded.Toll
|
||||||
|
Dimension.Data -> Icons.Rounded.Storage
|
||||||
|
Dimension.Bitrate -> TODO()
|
||||||
|
Dimension.Pressure -> TODO()
|
||||||
|
Dimension.Energy -> Icons.Rounded.Bolt
|
||||||
|
Dimension.Frequency -> TODO()
|
||||||
|
Dimension.Temperature -> Icons.Rounded.Thermostat
|
||||||
|
Dimension.Time -> Icons.Rounded.Schedule
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.search.website
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import de.mm20.launcher2.search.Website
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
|
||||||
|
|
||||||
|
fun LazyListScope.WebsiteResults(
|
||||||
|
websites: List<Website>,
|
||||||
|
selectedIndex: Int,
|
||||||
|
onSelect: (Int) -> Unit,
|
||||||
|
highlightedItem: Website?,
|
||||||
|
reverse: Boolean,
|
||||||
|
) {
|
||||||
|
ListResults(
|
||||||
|
key = "website",
|
||||||
|
items = websites,
|
||||||
|
itemContent = { website, showDetails, index ->
|
||||||
|
ListItem(
|
||||||
|
item = website,
|
||||||
|
showDetails = showDetails,
|
||||||
|
onShowDetails = { onSelect(if(it) index else -1) },
|
||||||
|
highlight = website.key == highlightedItem?.key,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
selectedIndex = selectedIndex,
|
||||||
|
reverse = reverse,
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -61,9 +61,7 @@ fun ArticleItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.clickable {
|
modifier = modifier
|
||||||
viewModel.launch(context)
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
if (!article.imageUrl.isNullOrEmpty()) {
|
if (!article.imageUrl.isNullOrEmpty()) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.search.wikipedia
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import de.mm20.launcher2.search.Article
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
|
||||||
|
|
||||||
|
fun LazyListScope.ArticleResults(
|
||||||
|
articles: List<Article>,
|
||||||
|
selectedIndex: Int,
|
||||||
|
onSelect: (Int) -> Unit,
|
||||||
|
highlightedItem: Article?,
|
||||||
|
reverse: Boolean,
|
||||||
|
) {
|
||||||
|
ListResults(
|
||||||
|
key = "article",
|
||||||
|
items = articles,
|
||||||
|
itemContent = { article, showDetails, index ->
|
||||||
|
ListItem(
|
||||||
|
item = article,
|
||||||
|
showDetails = showDetails,
|
||||||
|
onShowDetails = { onSelect(if(it) index else -1) },
|
||||||
|
highlight = article.key == highlightedItem?.key,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
selectedIndex = selectedIndex,
|
||||||
|
reverse = reverse,
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user