Add location tap feature
This commit is contained in:
parent
6e56313639
commit
81259cabc9
5 changed files with 199 additions and 50 deletions
|
|
@ -41,6 +41,7 @@ fun FantasyMap(
|
||||||
item: State<LocationDetailUio>,
|
item: State<LocationDetailUio>,
|
||||||
selectedItem: State<Int>,
|
selectedItem: State<Int>,
|
||||||
onMarquee: (MarqueeUio) -> Unit,
|
onMarquee: (MarqueeUio) -> Unit,
|
||||||
|
onTap: (Offset) -> Unit,
|
||||||
) {
|
) {
|
||||||
val colorScheme = MaterialTheme.lexicon.colorScheme
|
val colorScheme = MaterialTheme.lexicon.colorScheme
|
||||||
val animatedScale = animateFloatAsState(targetValue = state.scale, label = "ScaleAnimation")
|
val animatedScale = animateFloatAsState(targetValue = state.scale, label = "ScaleAnimation")
|
||||||
|
|
@ -84,47 +85,63 @@ fun FantasyMap(
|
||||||
.onSizeChanged { state.imageSize = it }
|
.onSizeChanged { state.imageSize = it }
|
||||||
.drawWithContent {
|
.drawWithContent {
|
||||||
drawContent()
|
drawContent()
|
||||||
item.value.marquees.forEachIndexed { index, item ->
|
|
||||||
if (item.position != Offset.Unspecified) {
|
if (state.freeHand.not()) {
|
||||||
drawCircle(
|
item.value.marquees.forEachIndexed { index, item ->
|
||||||
color = colorScheme.shadow,
|
if (item.position != Offset.Unspecified) {
|
||||||
radius = 12.dp.toPx() / animatedScale.value,
|
drawCircle(
|
||||||
style = Stroke(
|
color = colorScheme.shadow,
|
||||||
width = 2.dp.toPx() / animatedScale.value,
|
radius = 12.dp.toPx() / animatedScale.value,
|
||||||
),
|
style = Stroke(
|
||||||
center = Offset(
|
width = 2.dp.toPx() / animatedScale.value,
|
||||||
x = size.width * item.position.x,
|
),
|
||||||
y = size.height * item.position.y + 2.dp.toPx() / animatedScale.value,
|
center = Offset(
|
||||||
|
x = size.width * item.position.x,
|
||||||
|
y = size.height * item.position.y + 2.dp.toPx() / animatedScale.value,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
drawCircle(
|
||||||
drawCircle(
|
color = when (selectedItem.value) {
|
||||||
color = when (selectedItem.value) {
|
index -> colorScheme.base.primary
|
||||||
index -> colorScheme.base.primary
|
else -> Color.White
|
||||||
else -> Color.White
|
},
|
||||||
},
|
radius = 12.dp.toPx() / animatedScale.value,
|
||||||
radius = 12.dp.toPx() / animatedScale.value,
|
style = Stroke(
|
||||||
style = Stroke(
|
width = 2.dp.toPx() / animatedScale.value,
|
||||||
width = 2.dp.toPx() / animatedScale.value,
|
),
|
||||||
),
|
center = Offset(
|
||||||
center = Offset(
|
x = size.width * item.position.x,
|
||||||
x = size.width * item.position.x,
|
y = size.height * item.position.y,
|
||||||
y = size.height * item.position.y,
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pointerInput("DetectTapGestures") {
|
.pointerInput("DetectTapGestures") {
|
||||||
detectTapGestures(
|
detectTapGestures(
|
||||||
onTap = { tap ->
|
onTap = { tap ->
|
||||||
val marquee = item.value.marquees
|
if (state.freeHand) {
|
||||||
.asReversed()
|
onTap(
|
||||||
.firstOrNull { item ->
|
Offset(
|
||||||
val radius = 24.dp.toPx() / animatedScale.value
|
x = tap.x / size.width,
|
||||||
(size.width * item.position.x).let { tap.x in (it - radius)..(it + radius) } &&
|
y = tap.y / size.height,
|
||||||
(size.height * item.position.y).let { tap.y in (it - radius)..(it + radius) }
|
)
|
||||||
}
|
)
|
||||||
marquee?.let(onMarquee)
|
} else {
|
||||||
|
val marquee = item.value.marquees
|
||||||
|
.asReversed()
|
||||||
|
.firstOrNull { item ->
|
||||||
|
if (item.position != Offset.Unspecified) {
|
||||||
|
val radius = 24.dp.toPx() / animatedScale.value
|
||||||
|
(size.width * item.position.x).let { tap.x in (it - radius)..(it + radius) } &&
|
||||||
|
(size.height * item.position.y).let { tap.y in (it - radius)..(it + radius) }
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
marquee?.let(onMarquee)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
@ -174,6 +191,9 @@ class FantasyMapState(
|
||||||
private val _imageSize: MutableState<IntSize> = mutableStateOf(IntSize.Zero)
|
private val _imageSize: MutableState<IntSize> = mutableStateOf(IntSize.Zero)
|
||||||
var imageSize: IntSize by _imageSize
|
var imageSize: IntSize by _imageSize
|
||||||
|
|
||||||
|
private val _freeHand: MutableState<Boolean> = mutableStateOf(false)
|
||||||
|
val freeHand: Boolean by _freeHand
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
fun scale(
|
fun scale(
|
||||||
scale: Float,
|
scale: Float,
|
||||||
|
|
@ -193,6 +213,10 @@ class FantasyMapState(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toggleFreeHand(toggle: Boolean) {
|
||||||
|
_freeHand.value = toggle
|
||||||
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
fun computeMarqueeOffset(
|
fun computeMarqueeOffset(
|
||||||
origin: Offset,
|
origin: Offset,
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,14 @@ import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FilledIconButton
|
import androidx.compose.material3.FilledIconButton
|
||||||
|
import androidx.compose.material3.FilledIconToggleButton
|
||||||
|
import androidx.compose.material3.FilledTonalIconButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.IconButtonDefaults
|
import androidx.compose.material3.IconButtonDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
|
|
@ -48,15 +51,19 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.pixelized.rplexicon.LocalSnack
|
||||||
import com.pixelized.rplexicon.R
|
import com.pixelized.rplexicon.R
|
||||||
import com.pixelized.rplexicon.ui.composable.Handle
|
import com.pixelized.rplexicon.ui.composable.Handle
|
||||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
|
import com.pixelized.rplexicon.utilitary.rememberTextSize
|
||||||
import com.skydoves.landscapist.ImageOptions
|
import com.skydoves.landscapist.ImageOptions
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
@ -73,11 +80,16 @@ data class LocationDetailUio(
|
||||||
fun LocationDetail(
|
fun LocationDetail(
|
||||||
viewModel: LocationDetailViewModel = hiltViewModel()
|
viewModel: LocationDetailViewModel = hiltViewModel()
|
||||||
) {
|
) {
|
||||||
|
val screen = LocalScreenNavHost.current
|
||||||
|
val snack = LocalSnack.current
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val scroll = rememberScrollState()
|
val scroll = rememberScrollState()
|
||||||
val pager = rememberPagerState()
|
val pager = rememberPagerState()
|
||||||
val fantasy = rememberFantasyMapState()
|
val fantasy = rememberFantasyMapState()
|
||||||
val screen = LocalScreenNavHost.current
|
|
||||||
|
val ok = stringResource(id = android.R.string.ok)
|
||||||
|
val job = remember { mutableStateOf<Job?>(null) }
|
||||||
|
|
||||||
val connection = remember {
|
val connection = remember {
|
||||||
object : NestedScrollConnection {
|
object : NestedScrollConnection {
|
||||||
|
|
@ -117,6 +129,19 @@ fun LocationDetail(
|
||||||
pager.animateScrollToPage(page = index)
|
pager.animateScrollToPage(page = index)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onMapTap = {
|
||||||
|
job.value?.cancel()
|
||||||
|
job.value = scope.launch {
|
||||||
|
snack.showSnackbar(
|
||||||
|
message = "x:${it.x}, y:${it.y}",
|
||||||
|
actionLabel = ok,
|
||||||
|
duration = SnackbarDuration.Indefinite,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTouch = {
|
||||||
|
fantasy.toggleFreeHand(it)
|
||||||
|
},
|
||||||
onCenter = {
|
onCenter = {
|
||||||
fantasy.scale(scale = 1f)
|
fantasy.scale(scale = 1f)
|
||||||
fantasy.pan(offset = Offset.Zero)
|
fantasy.pan(offset = Offset.Zero)
|
||||||
|
|
@ -176,10 +201,26 @@ private fun LocationContent(
|
||||||
selectedIndex: State<Int>,
|
selectedIndex: State<Int>,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onMarquee: (MarqueeUio) -> Unit,
|
onMarquee: (MarqueeUio) -> Unit,
|
||||||
|
onMapTap: (Offset) -> Unit,
|
||||||
|
onTouch: (Boolean) -> Unit,
|
||||||
onCenter: () -> Unit,
|
onCenter: () -> Unit,
|
||||||
onZoomIn: () -> Unit,
|
onZoomIn: () -> Unit,
|
||||||
onZoomOut: () -> Unit,
|
onZoomOut: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val itemNameSize = rememberTextSize(style = MaterialTheme.typography.headlineSmall)
|
||||||
|
|
||||||
|
val filledIconButtonColors = IconButtonDefaults.filledIconButtonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
|
||||||
|
val filledIconToggleButtonColors = IconButtonDefaults.filledIconToggleButtonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
checkedContainerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
checkedContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
topBar = {
|
topBar = {
|
||||||
|
|
@ -226,19 +267,31 @@ private fun LocationContent(
|
||||||
item = item,
|
item = item,
|
||||||
selectedItem = selectedIndex,
|
selectedItem = selectedIndex,
|
||||||
onMarquee = onMarquee,
|
onMarquee = onMarquee,
|
||||||
|
onTap = onMapTap,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FilledIconToggleButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(alignment = Alignment.TopEnd)
|
||||||
|
.padding(all = 16.dp),
|
||||||
|
checked = fantasyMapState.freeHand,
|
||||||
|
onCheckedChange = onTouch,
|
||||||
|
colors = filledIconToggleButtonColors,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_baseline_touch_app_24),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(alignment = Alignment.BottomEnd)
|
.align(alignment = Alignment.BottomEnd)
|
||||||
.padding(all = 16.dp),
|
.padding(all = 16.dp),
|
||||||
) {
|
) {
|
||||||
val colors = IconButtonDefaults.filledIconButtonColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
|
||||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
FilledIconButton(
|
FilledIconButton(
|
||||||
onClick = onZoomOut,
|
onClick = onZoomOut,
|
||||||
colors = colors,
|
colors = filledIconButtonColors,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.ic_baseline_remove_24),
|
painter = painterResource(id = R.drawable.ic_baseline_remove_24),
|
||||||
|
|
@ -247,7 +300,7 @@ private fun LocationContent(
|
||||||
}
|
}
|
||||||
FilledIconButton(
|
FilledIconButton(
|
||||||
onClick = onZoomIn,
|
onClick = onZoomIn,
|
||||||
colors = colors,
|
colors = filledIconButtonColors,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.ic_baseline_add_24),
|
painter = painterResource(id = R.drawable.ic_baseline_add_24),
|
||||||
|
|
@ -256,7 +309,7 @@ private fun LocationContent(
|
||||||
}
|
}
|
||||||
FilledIconButton(
|
FilledIconButton(
|
||||||
onClick = onCenter,
|
onClick = onCenter,
|
||||||
colors = colors,
|
colors = filledIconButtonColors,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.ic_baseline_zoom_in_map_24),
|
painter = painterResource(id = R.drawable.ic_baseline_zoom_in_map_24),
|
||||||
|
|
@ -273,6 +326,16 @@ private fun LocationContent(
|
||||||
.padding(top = 16.dp)
|
.padding(top = 16.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(alignment = Alignment.CenterHorizontally)
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.padding(top = 16.dp),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
text = item.value.name,
|
||||||
|
)
|
||||||
|
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
@ -287,7 +350,7 @@ private fun LocationContent(
|
||||||
MarqueeItem(
|
MarqueeItem(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(this@constraint.maxHeight - 32.dp),
|
.height(this@constraint.maxHeight - 32.dp - itemNameSize.height),
|
||||||
marquee = marquee,
|
marquee = marquee,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -351,6 +414,8 @@ private fun LocationPreview() {
|
||||||
selectedIndex = remember { mutableStateOf(0) },
|
selectedIndex = remember { mutableStateOf(0) },
|
||||||
onBack = { },
|
onBack = { },
|
||||||
onMarquee = { },
|
onMarquee = { },
|
||||||
|
onMapTap = { },
|
||||||
|
onTouch = { },
|
||||||
onCenter = { },
|
onCenter = { },
|
||||||
onZoomIn = { },
|
onZoomIn = { },
|
||||||
onZoomOut = { },
|
onZoomOut = { },
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@ import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
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.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
|
@ -39,7 +41,7 @@ fun MarqueeItem(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
text = marquee.name,
|
text = marquee.name,
|
||||||
)
|
)
|
||||||
marquee.description?.let {
|
marquee.description?.let {
|
||||||
|
|
@ -56,13 +58,15 @@ fun MarqueeItem(
|
||||||
@Preview
|
@Preview
|
||||||
private fun MarqueeItemPreview() {
|
private fun MarqueeItemPreview() {
|
||||||
LexiconTheme {
|
LexiconTheme {
|
||||||
MarqueeItem(
|
Surface {
|
||||||
modifier = Modifier.fillMaxSize(),
|
MarqueeItem(
|
||||||
marquee = MarqueeUio(
|
modifier = Modifier.padding(all = 16.dp),
|
||||||
name = "Name",
|
marquee = MarqueeUio(
|
||||||
position = Offset.Zero,
|
name = "Name",
|
||||||
description = "description",
|
position = Offset.Zero,
|
||||||
|
description = "description",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.pixelized.rplexicon.utilitary
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.TextUnit
|
||||||
|
|
||||||
|
private const val PT_TO_PX_RATIO = 4f / 3f // pt to px ratio
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TextMeasurement class
|
||||||
|
*
|
||||||
|
* Currently it is impossible to set a *minLine* value on a Text in Compose.
|
||||||
|
* This class is a work around that compute the require Height for a [TextStyle].
|
||||||
|
*
|
||||||
|
* google issue tracker :
|
||||||
|
* https://issuetracker.google.com/issues/122476634
|
||||||
|
*
|
||||||
|
* based on :
|
||||||
|
* https://stackoverflow.com/questions/66394624/specify-minimal-lines-for-text-in-jetpack-compose
|
||||||
|
*/
|
||||||
|
@Stable
|
||||||
|
@Immutable
|
||||||
|
data class TextMeasurement(
|
||||||
|
val height: Dp,
|
||||||
|
val line: TextUnit,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberTextSize(
|
||||||
|
style: TextStyle,
|
||||||
|
): TextMeasurement {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
return remember(density, style) {
|
||||||
|
val line = if (style.lineHeight != TextUnit.Unspecified) {
|
||||||
|
style.lineHeight
|
||||||
|
} else {
|
||||||
|
style.fontSize * PT_TO_PX_RATIO
|
||||||
|
}
|
||||||
|
val height = with(density) { line.toDp() }
|
||||||
|
|
||||||
|
TextMeasurement(
|
||||||
|
height = height,
|
||||||
|
line = line,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
5
app/src/main/res/drawable/ic_baseline_touch_app_24.xml
Normal file
5
app/src/main/res/drawable/ic_baseline_touch_app_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M9,11.24V7.5C9,6.12 10.12,5 11.5,5S14,6.12 14,7.5v3.74c1.21,-0.81 2,-2.18 2,-3.74C16,5.01 13.99,3 11.5,3S7,5.01 7,7.5C7,9.06 7.79,10.43 9,11.24zM18.84,15.87l-4.54,-2.26c-0.17,-0.07 -0.35,-0.11 -0.54,-0.11H13v-6C13,6.67 12.33,6 11.5,6S10,6.67 10,7.5v10.74c-3.6,-0.76 -3.54,-0.75 -3.67,-0.75c-0.31,0 -0.59,0.13 -0.79,0.33l-0.79,0.8l4.94,4.94C9.96,23.83 10.34,24 10.75,24h6.79c0.75,0 1.33,-0.55 1.44,-1.28l0.75,-5.27c0.01,-0.07 0.02,-0.14 0.02,-0.2C19.75,16.63 19.37,16.09 18.84,15.87z"/>
|
||||||
|
</vector>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue