feat(incognito): add animated incognito mode indicator and icon (#899)

* Add indicator for source incognito mode

Adds an indicator to the manga info header to show if a source is running in incognito mode.
Also moves the source incognito check logic to `SourceExtensions`.

(cherry picked from commit 5e547efda51cdeaa7bc71198b932c8ac9aecc312)

* Replaced incognito icon with animated glasses_with_hat icon.

This commit introduces an animated vector drawable for the incognito mode icon and updates the related icons in the app.

- Adds `anim_incognito.xml`, a new animated vector drawable for incognito mode.
- Adds `ic_glasses_with_hat_24dp.xml`, a new icon for incognito mode.
- Updates the incognito mode icon in the following places:
    - MoreScreen
    - ExtensionDetailsScreen
    - App notification
    - Manga info header
- Updates `SwitchPreferenceWidget` and `TextPreferenceWidget` to accept any type of icons.

(cherry picked from commit 2a28673e078ff15fe0a7c2a030ffc6ce6cbe6b31)

* move incognito icon

* issue: Use stringResource for contentDescription to support localization

* Wrap the ImageVector.vectorResource call in remember to avoid inflating the drawable on every recomposition.

---------

Co-authored-by: Cuong-Tran <cuongtran.tm@gmail.com>
This commit is contained in:
Jery 2025-05-11 02:37:15 +07:00 committed by Cuong-Tran
parent 4fcbd88618
commit 3d9dd9d7b1
No known key found for this signature in database
GPG key ID: 733AA7624B9315C2
10 changed files with 168 additions and 13 deletions

View file

@ -4,6 +4,9 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.provider.Settings import android.provider.Settings
import android.util.DisplayMetrics import android.util.DisplayMetrics
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -36,10 +39,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@ -374,7 +375,12 @@ private fun DetailsHeader(
modifier = Modifier.padding(horizontal = MaterialTheme.padding.small), modifier = Modifier.padding(horizontal = MaterialTheme.padding.small),
title = stringResource(MR.strings.pref_incognito_mode), title = stringResource(MR.strings.pref_incognito_mode),
subtitle = stringResource(MR.strings.pref_incognito_mode_extension_summary), subtitle = stringResource(MR.strings.pref_incognito_mode_extension_summary),
icon = ImageVector.vectorResource(R.drawable.ic_glasses_24dp), // KMK -->
icon = rememberAnimatedVectorPainter(
AnimatedImageVector.animatedVectorResource(R.drawable.anim_incognito),
extIncognitoMode,
),
// KMK <--
widget = { widget = {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,

View file

@ -88,6 +88,7 @@ import eu.kanade.presentation.util.formatChapterNumber
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.getNameForMangaInfo import eu.kanade.tachiyomi.source.getNameForMangaInfo
import eu.kanade.tachiyomi.source.isIncognitoModeEnabled
import eu.kanade.tachiyomi.source.online.MetadataSource import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.source.online.all.MangaDex
@ -621,6 +622,9 @@ private fun MangaScreenSmallImpl(
manga = state.manga, manga = state.manga,
sourceName = remember { state.source.getNameForMangaInfo(state.mergedData?.sources) }, sourceName = remember { state.source.getNameForMangaInfo(state.mergedData?.sources) },
isStubSource = remember { state.source is StubSource }, isStubSource = remember { state.source is StubSource },
// KMK -->
isSourceIncognito = remember { state.source.isIncognitoModeEnabled() },
// KMK <--
onCoverClick = onCoverClicked, onCoverClick = onCoverClicked,
doSearch = onSearch, doSearch = onSearch,
// KMK --> // KMK -->
@ -1075,6 +1079,9 @@ private fun MangaScreenLargeImpl(
manga = state.manga, manga = state.manga,
sourceName = remember { state.source.getNameForMangaInfo(state.mergedData?.sources) }, sourceName = remember { state.source.getNameForMangaInfo(state.mergedData?.sources) },
isStubSource = remember { state.source is StubSource }, isStubSource = remember { state.source is StubSource },
// KMK -->
isSourceIncognito = remember { state.source.isIncognitoModeEnabled() },
// KMK <--
onCoverClick = onCoverClicked, onCoverClick = onCoverClicked,
doSearch = onSearch, doSearch = onSearch,
// KMK --> // KMK -->

View file

@ -67,9 +67,11 @@ import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Constraints
@ -115,6 +117,9 @@ fun MangaInfoBox(
manga: Manga, manga: Manga,
sourceName: String, sourceName: String,
isStubSource: Boolean, isStubSource: Boolean,
// KMK -->
isSourceIncognito: Boolean,
// KMK <--
onCoverClick: () -> Unit, onCoverClick: () -> Unit,
doSearch: (query: String, global: Boolean) -> Unit, doSearch: (query: String, global: Boolean) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -177,6 +182,9 @@ fun MangaInfoBox(
manga = manga, manga = manga,
sourceName = sourceName, sourceName = sourceName,
isStubSource = isStubSource, isStubSource = isStubSource,
// KMK -->
isSourceIncognito = isSourceIncognito,
// KMK <--
onCoverClick = onCoverClick, onCoverClick = onCoverClick,
doSearch = doSearch, doSearch = doSearch,
// KMK --> // KMK -->
@ -194,6 +202,9 @@ fun MangaInfoBox(
manga = manga, manga = manga,
sourceName = sourceName, sourceName = sourceName,
isStubSource = isStubSource, isStubSource = isStubSource,
// KMK -->
isSourceIncognito = isSourceIncognito,
// KMK <--
onCoverClick = onCoverClick, onCoverClick = onCoverClick,
doSearch = doSearch, doSearch = doSearch,
// KMK --> // KMK -->
@ -469,6 +480,9 @@ private fun MangaAndSourceTitlesLarge(
manga: Manga, manga: Manga,
sourceName: String, sourceName: String,
isStubSource: Boolean, isStubSource: Boolean,
// KMK -->
isSourceIncognito: Boolean,
// KMK <--
onCoverClick: () -> Unit, onCoverClick: () -> Unit,
doSearch: (query: String, global: Boolean) -> Unit, doSearch: (query: String, global: Boolean) -> Unit,
// KMK --> // KMK -->
@ -530,6 +544,9 @@ private fun MangaAndSourceTitlesLarge(
status = manga.status, status = manga.status,
sourceName = sourceName, sourceName = sourceName,
isStubSource = isStubSource, isStubSource = isStubSource,
// KMK -->
isSourceIncognito = isSourceIncognito,
// KMK <--
doSearch = doSearch, doSearch = doSearch,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
// KMK --> // KMK -->
@ -546,6 +563,9 @@ private fun MangaAndSourceTitlesSmall(
manga: Manga, manga: Manga,
sourceName: String, sourceName: String,
isStubSource: Boolean, isStubSource: Boolean,
// KMK -->
isSourceIncognito: Boolean,
// KMK <--
onCoverClick: () -> Unit, onCoverClick: () -> Unit,
doSearch: (query: String, global: Boolean) -> Unit, doSearch: (query: String, global: Boolean) -> Unit,
// KMK --> // KMK -->
@ -619,6 +639,9 @@ private fun MangaAndSourceTitlesSmall(
status = manga.status, status = manga.status,
sourceName = sourceName, sourceName = sourceName,
isStubSource = isStubSource, isStubSource = isStubSource,
// KMK -->
isSourceIncognito = isSourceIncognito,
// KMK <--
doSearch = doSearch, doSearch = doSearch,
// KMK --> // KMK -->
librarySearch = librarySearch, librarySearch = librarySearch,
@ -638,6 +661,9 @@ private fun ColumnScope.MangaContentInfo(
status: Long, status: Long,
sourceName: String, sourceName: String,
isStubSource: Boolean, isStubSource: Boolean,
// KMK -->
isSourceIncognito: Boolean,
// KMK <--
doSearch: (query: String, global: Boolean) -> Unit, doSearch: (query: String, global: Boolean) -> Unit,
textAlign: TextAlign? = LocalTextStyle.current.textAlign, textAlign: TextAlign? = LocalTextStyle.current.textAlign,
// KMK --> // KMK -->
@ -792,6 +818,19 @@ private fun ColumnScope.MangaContentInfo(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 1, maxLines = 1,
) )
// KMK -->
if (isSourceIncognito) {
DotSeparatorText()
Icon(
painter = rememberVectorPainter(ImageVector.vectorResource(R.drawable.ic_glasses_with_hat_24dp)),
contentDescription = stringResource(MR.strings.pref_incognito_mode),
modifier = Modifier
.padding(end = 4.dp)
.size(16.dp),
tint = MaterialTheme.colorScheme.primary,
)
}
// KMK <--
DotSeparatorText() DotSeparatorText()
if (isStubSource) { if (isStubSource) {
Icon( Icon(

View file

@ -1,5 +1,8 @@
package eu.kanade.presentation.more package eu.kanade.presentation.more
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.automirrored.outlined.Label import androidx.compose.material.icons.automirrored.outlined.Label
@ -15,9 +18,7 @@ import androidx.compose.material.icons.outlined.Storage
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.vectorResource
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -86,7 +87,12 @@ fun MoreScreen(
SwitchPreferenceWidget( SwitchPreferenceWidget(
title = stringResource(MR.strings.pref_incognito_mode), title = stringResource(MR.strings.pref_incognito_mode),
subtitle = stringResource(MR.strings.pref_incognito_mode_summary), subtitle = stringResource(MR.strings.pref_incognito_mode_summary),
icon = ImageVector.vectorResource(R.drawable.ic_glasses_24dp), // KMK -->
icon = rememberAnimatedVectorPainter(
AnimatedImageVector.animatedVectorResource(R.drawable.anim_incognito),
incognitoMode,
),
// KMK <--
checked = incognitoMode, checked = incognitoMode,
onCheckedChanged = onIncognitoModeChange, onCheckedChanged = onIncognitoModeChange,
) )

View file

@ -8,6 +8,7 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewLightDark
import eu.kanade.presentation.theme.TachiyomiPreviewTheme import eu.kanade.presentation.theme.TachiyomiPreviewTheme
@ -17,7 +18,10 @@ fun SwitchPreferenceWidget(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
title: String, title: String,
subtitle: CharSequence? = null, subtitle: CharSequence? = null,
icon: ImageVector? = null, // KMK -->
/** Can be either [ImageVector] or [Painter] */
icon: Any? = null,
// KMK <--
checked: Boolean = false, checked: Boolean = false,
onCheckedChanged: (Boolean) -> Unit, onCheckedChanged: (Boolean) -> Unit,
) { ) {

View file

@ -11,6 +11,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
@ -25,7 +26,10 @@ fun TextPreferenceWidget(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
title: String? = null, title: String? = null,
subtitle: CharSequence? = null, subtitle: CharSequence? = null,
icon: ImageVector? = null, // KMK -->
/** Can be either [ImageVector] or [Painter] */
icon: Any? = null,
// KMK <--
iconTint: Color = MaterialTheme.colorScheme.primary, iconTint: Color = MaterialTheme.colorScheme.primary,
widget: @Composable (() -> Unit)? = null, widget: @Composable (() -> Unit)? = null,
onPreferenceClick: (() -> Unit)? = null, onPreferenceClick: (() -> Unit)? = null,
@ -60,16 +64,22 @@ fun TextPreferenceWidget(
} else { } else {
null null
}, },
icon = if (icon != null) { icon = {
{ if (icon != null && icon is ImageVector) {
Icon( Icon(
imageVector = icon, imageVector = icon,
tint = iconTint, tint = iconTint,
contentDescription = null, contentDescription = null,
) )
// KMK -->
} else if (icon != null && icon is Painter) {
Icon(
painter = icon,
tint = iconTint,
contentDescription = null,
)
// KMK <--
} }
} else {
null
}, },
onClick = onPreferenceClick, onClick = onPreferenceClick,
widget = widget, widget = widget,

View file

@ -164,7 +164,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
) { ) {
setContentTitle(stringResource(MR.strings.pref_incognito_mode)) setContentTitle(stringResource(MR.strings.pref_incognito_mode))
setContentText(stringResource(MR.strings.notification_incognito_text)) setContentText(stringResource(MR.strings.notification_incognito_text))
setSmallIcon(R.drawable.ic_glasses_24dp) setSmallIcon(R.drawable.ic_glasses_with_hat_24dp)
setColor(ContextCompat.getColor(applicationContext, R.color.ic_launcher)) setColor(ContextCompat.getColor(applicationContext, R.color.ic_launcher))
setLargeIcon(BitmapFactory.decodeResource(applicationContext.resources, R.drawable.komikku)) setLargeIcon(BitmapFactory.decodeResource(applicationContext.resources, R.drawable.komikku))
setOngoing(true) setOngoing(true)

View file

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.source package eu.kanade.tachiyomi.source
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.ExtensionManager
import tachiyomi.domain.source.model.StubSource import tachiyomi.domain.source.model.StubSource
import tachiyomi.presentation.core.icons.FlagEmoji import tachiyomi.presentation.core.icons.FlagEmoji
import tachiyomi.source.local.isLocal import tachiyomi.source.local.isLocal
@ -77,3 +78,10 @@ private fun getMergedSourcesString(
// SY <-- // SY <--
fun Source.isLocalOrStub(): Boolean = isLocal() || this is StubSource fun Source.isLocalOrStub(): Boolean = isLocal() || this is StubSource
// KMK -->
fun Source.isIncognitoModeEnabled(): Boolean {
val extensionPackage = Injekt.get<ExtensionManager>().getExtensionPackage(id)
return extensionPackage in Injekt.get<SourcePreferences>().incognitoExtensions().get()
}
// KMK <--

View file

@ -0,0 +1,52 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:name="hat"
android:pivotX="12"
android:pivotY="12"
android:scaleX="1.2"
android:scaleY="1.2">
<path
android:fillColor="#000"
android:pathData="M15.53 2.63C15.31 2.14 14.75 1.88 14.22 2.05L12 2.79L9.77 2.05L9.72 2.04C9.19 1.89 8.63 2.17 8.43 2.68L6 9H18L15.56 2.68L15.53 2.63Z M22 10.5H2V12H22V10.5Z M0 0L0 0Z" />
</group>
<group
android:name="glasses"
android:translateY="5">
<path
android:fillColor="#000"
android:pathData="M3,10C2.76,10 2.55,10.09 2.41,10.25C2.27,10.4 2.21,10.62 2.24,10.86L2.74,13.85C2.82,14.5 3.4,15 4,15H7C7.64,15 8.36,14.44 8.5,13.82L9.56,10.63C9.6,10.5 9.57,10.31 9.5,10.19C9.39,10.07 9.22,10 9,10H3M7,17H4C2.38,17 0.96,15.74 0.76,14.14L0.26,11.15C0.15,10.3 0.39,9.5 0.91,8.92C1.43,8.34 2.19,8 3,8H9C9.83,8 10.58,8.35 11.06,8.96C11.17,9.11 11.27,9.27 11.35,9.45C11.78,9.36 12.22,9.36 12.64,9.45C12.72,9.27 12.82,9.11 12.94,8.96C13.41,8.35 14.16,8 15,8H21C21.81,8 22.57,8.34 23.09,8.92C23.6,9.5 23.84,10.3 23.74,11.11L23.23,14.18C23.04,15.74 21.61,17 20,17H17C15.44,17 13.92,15.81 13.54,14.3L12.64,11.59C12.26,11.31 11.73,11.31 11.35,11.59L10.43,14.37C10.07,15.82 8.56,17 7,17M15,10C14.78,10 14.61,10.07 14.5,10.19C14.42,10.31 14.4,10.5 14.45,10.7L15.46,13.75C15.64,14.44 16.36,15 17,15H20C20.59,15 21.18,14.5 21.25,13.89L21.76,10.82C21.79,10.62 21.73,10.4 21.59,10.25C21.45,10.09 21.24,10 21,10H15Z" />
</group>
</vector>
</aapt:attr>
<target android:name="hat">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="300"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="scaleY"
android:valueFrom="0"
android:valueTo="1.2"
android:valueType="floatType" />
</aapt:attr>
</target>
<target android:name="glasses">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="300"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="translateY"
android:valueFrom="0"
android:valueTo="5"
android:valueType="floatType" />
</aapt:attr>
</target>
</animated-vector>

View file

@ -0,0 +1,23 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:name="hat"
android:pivotX="12"
android:pivotY="10"
android:scaleX="1.2"
android:scaleY="1.2">
<path
android:fillColor="#FFF"
android:pathData="M15.53 2.63C15.31 2.14 14.75 1.88 14.22 2.05L12 2.79L9.77 2.05L9.72 2.04C9.19 1.89 8.63 2.17 8.43 2.68L6 9H18L15.56 2.68L15.53 2.63Z M22 10.5H2V12H22V10.5Z M0 0L0 0Z" />
</group>
<group
android:name="glasses"
android:translateY="5">
<path
android:fillColor="#FFF"
android:pathData="M3,10C2.76,10 2.55,10.09 2.41,10.25C2.27,10.4 2.21,10.62 2.24,10.86L2.74,13.85C2.82,14.5 3.4,15 4,15H7C7.64,15 8.36,14.44 8.5,13.82L9.56,10.63C9.6,10.5 9.57,10.31 9.5,10.19C9.39,10.07 9.22,10 9,10H3M7,17H4C2.38,17 0.96,15.74 0.76,14.14L0.26,11.15C0.15,10.3 0.39,9.5 0.91,8.92C1.43,8.34 2.19,8 3,8H9C9.83,8 10.58,8.35 11.06,8.96C11.17,9.11 11.27,9.27 11.35,9.45C11.78,9.36 12.22,9.36 12.64,9.45C12.72,9.27 12.82,9.11 12.94,8.96C13.41,8.35 14.16,8 15,8H21C21.81,8 22.57,8.34 23.09,8.92C23.6,9.5 23.84,10.3 23.74,11.11L23.23,14.18C23.04,15.74 21.61,17 20,17H17C15.44,17 13.92,15.81 13.54,14.3L12.64,11.59C12.26,11.31 11.73,11.31 11.35,11.59L10.43,14.37C10.07,15.82 8.56,17 7,17M15,10C14.78,10 14.61,10.07 14.5,10.19C14.42,10.31 14.4,10.5 14.45,10.7L15.46,13.75C15.64,14.44 16.36,15 17,15H20C20.59,15 21.18,14.5 21.25,13.89L21.76,10.82C21.79,10.62 21.73,10.4 21.59,10.25C21.45,10.09 21.24,10 21,10H15Z" />
</group>
</vector>