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:
parent
4fcbd88618
commit
3d9dd9d7b1
10 changed files with 168 additions and 13 deletions
|
|
@ -4,6 +4,9 @@ import android.content.Intent
|
|||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
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.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -36,10 +39,8 @@ 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.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
|
|
@ -374,7 +375,12 @@ private fun DetailsHeader(
|
|||
modifier = Modifier.padding(horizontal = MaterialTheme.padding.small),
|
||||
title = stringResource(MR.strings.pref_incognito_mode),
|
||||
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 = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ import eu.kanade.presentation.util.formatChapterNumber
|
|||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
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.all.EHentai
|
||||
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||
|
|
@ -621,6 +622,9 @@ private fun MangaScreenSmallImpl(
|
|||
manga = state.manga,
|
||||
sourceName = remember { state.source.getNameForMangaInfo(state.mergedData?.sources) },
|
||||
isStubSource = remember { state.source is StubSource },
|
||||
// KMK -->
|
||||
isSourceIncognito = remember { state.source.isIncognitoModeEnabled() },
|
||||
// KMK <--
|
||||
onCoverClick = onCoverClicked,
|
||||
doSearch = onSearch,
|
||||
// KMK -->
|
||||
|
|
@ -1075,6 +1079,9 @@ private fun MangaScreenLargeImpl(
|
|||
manga = state.manga,
|
||||
sourceName = remember { state.source.getNameForMangaInfo(state.mergedData?.sources) },
|
||||
isStubSource = remember { state.source is StubSource },
|
||||
// KMK -->
|
||||
isSourceIncognito = remember { state.source.isIncognitoModeEnabled() },
|
||||
// KMK <--
|
||||
onCoverClick = onCoverClicked,
|
||||
doSearch = onSearch,
|
||||
// KMK -->
|
||||
|
|
|
|||
|
|
@ -67,9 +67,11 @@ import androidx.compose.ui.draw.drawWithContent
|
|||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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.Layout
|
||||
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.TextOverflow
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
|
|
@ -115,6 +117,9 @@ fun MangaInfoBox(
|
|||
manga: Manga,
|
||||
sourceName: String,
|
||||
isStubSource: Boolean,
|
||||
// KMK -->
|
||||
isSourceIncognito: Boolean,
|
||||
// KMK <--
|
||||
onCoverClick: () -> Unit,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -177,6 +182,9 @@ fun MangaInfoBox(
|
|||
manga = manga,
|
||||
sourceName = sourceName,
|
||||
isStubSource = isStubSource,
|
||||
// KMK -->
|
||||
isSourceIncognito = isSourceIncognito,
|
||||
// KMK <--
|
||||
onCoverClick = onCoverClick,
|
||||
doSearch = doSearch,
|
||||
// KMK -->
|
||||
|
|
@ -194,6 +202,9 @@ fun MangaInfoBox(
|
|||
manga = manga,
|
||||
sourceName = sourceName,
|
||||
isStubSource = isStubSource,
|
||||
// KMK -->
|
||||
isSourceIncognito = isSourceIncognito,
|
||||
// KMK <--
|
||||
onCoverClick = onCoverClick,
|
||||
doSearch = doSearch,
|
||||
// KMK -->
|
||||
|
|
@ -469,6 +480,9 @@ private fun MangaAndSourceTitlesLarge(
|
|||
manga: Manga,
|
||||
sourceName: String,
|
||||
isStubSource: Boolean,
|
||||
// KMK -->
|
||||
isSourceIncognito: Boolean,
|
||||
// KMK <--
|
||||
onCoverClick: () -> Unit,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
// KMK -->
|
||||
|
|
@ -530,6 +544,9 @@ private fun MangaAndSourceTitlesLarge(
|
|||
status = manga.status,
|
||||
sourceName = sourceName,
|
||||
isStubSource = isStubSource,
|
||||
// KMK -->
|
||||
isSourceIncognito = isSourceIncognito,
|
||||
// KMK <--
|
||||
doSearch = doSearch,
|
||||
textAlign = TextAlign.Center,
|
||||
// KMK -->
|
||||
|
|
@ -546,6 +563,9 @@ private fun MangaAndSourceTitlesSmall(
|
|||
manga: Manga,
|
||||
sourceName: String,
|
||||
isStubSource: Boolean,
|
||||
// KMK -->
|
||||
isSourceIncognito: Boolean,
|
||||
// KMK <--
|
||||
onCoverClick: () -> Unit,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
// KMK -->
|
||||
|
|
@ -619,6 +639,9 @@ private fun MangaAndSourceTitlesSmall(
|
|||
status = manga.status,
|
||||
sourceName = sourceName,
|
||||
isStubSource = isStubSource,
|
||||
// KMK -->
|
||||
isSourceIncognito = isSourceIncognito,
|
||||
// KMK <--
|
||||
doSearch = doSearch,
|
||||
// KMK -->
|
||||
librarySearch = librarySearch,
|
||||
|
|
@ -638,6 +661,9 @@ private fun ColumnScope.MangaContentInfo(
|
|||
status: Long,
|
||||
sourceName: String,
|
||||
isStubSource: Boolean,
|
||||
// KMK -->
|
||||
isSourceIncognito: Boolean,
|
||||
// KMK <--
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
|
||||
// KMK -->
|
||||
|
|
@ -792,6 +818,19 @@ private fun ColumnScope.MangaContentInfo(
|
|||
overflow = TextOverflow.Ellipsis,
|
||||
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()
|
||||
if (isStubSource) {
|
||||
Icon(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
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.automirrored.outlined.HelpOutline
|
||||
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.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
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.TextPreferenceWidget
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
|
@ -86,7 +87,12 @@ fun MoreScreen(
|
|||
SwitchPreferenceWidget(
|
||||
title = stringResource(MR.strings.pref_incognito_mode),
|
||||
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,
|
||||
onCheckedChanged = onIncognitoModeChange,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.compose.material3.Surface
|
|||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
|
|
@ -17,7 +18,10 @@ fun SwitchPreferenceWidget(
|
|||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
subtitle: CharSequence? = null,
|
||||
icon: ImageVector? = null,
|
||||
// KMK -->
|
||||
/** Can be either [ImageVector] or [Painter] */
|
||||
icon: Any? = null,
|
||||
// KMK <--
|
||||
checked: Boolean = false,
|
||||
onCheckedChanged: (Boolean) -> Unit,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
|
|
@ -25,7 +26,10 @@ fun TextPreferenceWidget(
|
|||
modifier: Modifier = Modifier,
|
||||
title: String? = null,
|
||||
subtitle: CharSequence? = null,
|
||||
icon: ImageVector? = null,
|
||||
// KMK -->
|
||||
/** Can be either [ImageVector] or [Painter] */
|
||||
icon: Any? = null,
|
||||
// KMK <--
|
||||
iconTint: Color = MaterialTheme.colorScheme.primary,
|
||||
widget: @Composable (() -> Unit)? = null,
|
||||
onPreferenceClick: (() -> Unit)? = null,
|
||||
|
|
@ -60,16 +64,22 @@ fun TextPreferenceWidget(
|
|||
} else {
|
||||
null
|
||||
},
|
||||
icon = if (icon != null) {
|
||||
{
|
||||
icon = {
|
||||
if (icon != null && icon is ImageVector) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
tint = iconTint,
|
||||
contentDescription = null,
|
||||
)
|
||||
// KMK -->
|
||||
} else if (icon != null && icon is Painter) {
|
||||
Icon(
|
||||
painter = icon,
|
||||
tint = iconTint,
|
||||
contentDescription = null,
|
||||
)
|
||||
// KMK <--
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
onClick = onPreferenceClick,
|
||||
widget = widget,
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
|||
) {
|
||||
setContentTitle(stringResource(MR.strings.pref_incognito_mode))
|
||||
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))
|
||||
setLargeIcon(BitmapFactory.decodeResource(applicationContext.resources, R.drawable.komikku))
|
||||
setOngoing(true)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package eu.kanade.tachiyomi.source
|
||||
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import tachiyomi.domain.source.model.StubSource
|
||||
import tachiyomi.presentation.core.icons.FlagEmoji
|
||||
import tachiyomi.source.local.isLocal
|
||||
|
|
@ -77,3 +78,10 @@ private fun getMergedSourcesString(
|
|||
// SY <--
|
||||
|
||||
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 <--
|
||||
|
|
|
|||
52
app/src/main/res/drawable/anim_incognito.xml
Normal file
52
app/src/main/res/drawable/anim_incognito.xml
Normal 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>
|
||||
23
app/src/main/res/drawable/ic_glasses_with_hat_24dp.xml
Normal file
23
app/src/main/res/drawable/ic_glasses_with_hat_24dp.xml
Normal 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>
|
||||
Loading…
Reference in a new issue