piggy feature

This commit is contained in:
2026-06-24 20:25:05 +03:00
parent 0937d65943
commit 5946ffae6e
13 changed files with 295 additions and 14 deletions
@@ -19,7 +19,6 @@
android:enableOnBackInvokedCallback="true" android:enableOnBackInvokedCallback="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Videobox" android:theme="@style/Theme.Videobox"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
@@ -51,14 +50,54 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data
android:scheme="https" android:host="box.shadowsparky.ru"
android:host="box.shadowsparky.ru" /> android:scheme="https" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.app.shortcuts" android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" /> android:resource="@xml/shortcuts" />
</activity> </activity>
</application>
<activity-alias
android:name=".PiggyActivity"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/pig_launcher"
android:label="@string/app_name_pig"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="ru.shadowsparky.vbox.action.OPEN_SAVED_MOVIES" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="box.shadowsparky.ru"
android:scheme="https" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity-alias>
<receiver
android:name=".SecretCodeReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.provider.Telephony.SECRET_CODE" />
<data android:scheme="android_secret_code" android:host="676767" />
</intent-filter>
</receiver>
</application>
</manifest> </manifest>
@@ -9,15 +9,18 @@ import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.CompositionLocalProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.arkivanov.decompose.retainedComponent import com.arkivanov.decompose.retainedComponent
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.shadowsparky.koin import ru.shadowsparky.koin
import ru.shadowsparky.vbox.shared.presentation.LocalColorSchemeProvider
import ru.shadowsparky.vbox.shared.presentation.Nav import ru.shadowsparky.vbox.shared.presentation.Nav
import ru.shadowsparky.vbox.shared.presentation.RootComponent import ru.shadowsparky.vbox.shared.presentation.RootComponent
import ru.shadowsparky.vbox.shared.presentation.RootFactory import ru.shadowsparky.vbox.shared.presentation.RootFactory
import ru.shadowsparky.vbox.shared.presentation.UrlRoutes import ru.shadowsparky.vbox.shared.presentation.UrlRoutes
import ru.shadowsparky.vbox.shared.presentation.nav.Route import ru.shadowsparky.vbox.shared.presentation.nav.Route
import ru.shadowsparky.videobox.multiplatform.piggy.getPiggyColorScheme
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private lateinit var root: RootComponent private lateinit var root: RootComponent
@@ -39,8 +42,15 @@ class MainActivity : ComponentActivity() {
.setData(null) .setData(null)
.setAction(null) .setAction(null)
} }
val isPiggyActivity = componentName?.className == SecretCodeReceiver.PIGGY_CLS
setContent { setContent {
Nav(rootComponent = root) if (isPiggyActivity) {
CompositionLocalProvider(LocalColorSchemeProvider provides getPiggyColorScheme()) {
Nav(rootComponent = root)
}
} else {
Nav(rootComponent = root)
}
edgeToEdge(isSystemInDarkTheme()) edgeToEdge(isSystemInDarkTheme())
} }
lifecycleScope.launch { lifecycleScope.launch {
@@ -0,0 +1,31 @@
package ru.shadowsparky.videobox.multiplatform
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
class SecretCodeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (context == null || intent?.action != "android.provider.Telephony.SECRET_CODE") return
val pm = context.packageManager
val mainActivity = ComponentName(context, MainActivity::class.java)
val piggyActivityAlias = ComponentName(context, PIGGY_CLS)
val isMainEnabled = pm.getComponentEnabledSetting(mainActivity) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
pm.setState(piggyActivityAlias, isMainEnabled)
pm.setState(mainActivity, !isMainEnabled)
}
private fun PackageManager.setState(cn: ComponentName, isEnabled: Boolean) {
setComponentEnabledSetting(
cn,
if (isEnabled) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
}
companion object {
const val PIGGY_CLS = "ru.shadowsparky.videobox.multiplatform.PiggyActivity"
}
}
@@ -0,0 +1,75 @@
package ru.shadowsparky.videobox.multiplatform.piggy
import androidx.compose.ui.graphics.Color
val primaryLight = Color(0xFF8F4A4D)
val onPrimaryLight = Color(0xFFFFFFFF)
val primaryContainerLight = Color(0xFFFFDADA)
val onPrimaryContainerLight = Color(0xFF733337)
val secondaryLight = Color(0xFF765657)
val onSecondaryLight = Color(0xFFFFFFFF)
val secondaryContainerLight = Color(0xFFFFDADA)
val onSecondaryContainerLight = Color(0xFF5D3F40)
val tertiaryLight = Color(0xFF755A2F)
val onTertiaryLight = Color(0xFFFFFFFF)
val tertiaryContainerLight = Color(0xFFFFDDB0)
val onTertiaryContainerLight = Color(0xFF5B421A)
val errorLight = Color(0xFFBA1A1A)
val onErrorLight = Color(0xFFFFFFFF)
val errorContainerLight = Color(0xFFFFDAD6)
val onErrorContainerLight = Color(0xFF93000A)
val backgroundLight = Color(0xFFFFF8F7)
val onBackgroundLight = Color(0xFF221919)
val surfaceLight = Color(0xFFFFF8F7)
val onSurfaceLight = Color(0xFF221919)
val surfaceVariantLight = Color(0xFFF4DDDD)
val onSurfaceVariantLight = Color(0xFF524343)
val outlineLight = Color(0xFF857373)
val outlineVariantLight = Color(0xFFD7C1C1)
val scrimLight = Color(0xFF000000)
val inverseSurfaceLight = Color(0xFF382E2E)
val inverseOnSurfaceLight = Color(0xFFFFEDEC)
val inversePrimaryLight = Color(0xFFFFB3B4)
val surfaceDimLight = Color(0xFFE8D6D6)
val surfaceBrightLight = Color(0xFFFFF8F7)
val surfaceContainerLowestLight = Color(0xFFFFFFFF)
val surfaceContainerLowLight = Color(0xFFFFF0F0)
val surfaceContainerLight = Color(0xFFFCEAE9)
val surfaceContainerHighLight = Color(0xFFF6E4E4)
val surfaceContainerHighestLight = Color(0xFFF0DEDE)
val primaryDark = Color(0xFFFFB3B4)
val onPrimaryDark = Color(0xFF561D22)
val primaryContainerDark = Color(0xFF733337)
val onPrimaryContainerDark = Color(0xFFFFDADA)
val secondaryDark = Color(0xFFE6BDBD)
val onSecondaryDark = Color(0xFF44292A)
val secondaryContainerDark = Color(0xFF5D3F40)
val onSecondaryContainerDark = Color(0xFFFFDADA)
val tertiaryDark = Color(0xFFE6C18D)
val onTertiaryDark = Color(0xFF422C05)
val tertiaryContainerDark = Color(0xFF5B421A)
val onTertiaryContainerDark = Color(0xFFFFDDB0)
val errorDark = Color(0xFFFFB4AB)
val onErrorDark = Color(0xFF690005)
val errorContainerDark = Color(0xFF93000A)
val onErrorContainerDark = Color(0xFFFFDAD6)
val backgroundDark = Color(0xFF1A1111)
val onBackgroundDark = Color(0xFFF0DEDE)
val surfaceDark = Color(0xFF1A1111)
val onSurfaceDark = Color(0xFFF0DEDE)
val surfaceVariantDark = Color(0xFF524343)
val onSurfaceVariantDark = Color(0xFFD7C1C1)
val outlineDark = Color(0xFF9F8C8C)
val outlineVariantDark = Color(0xFF524343)
val scrimDark = Color(0xFF000000)
val inverseSurfaceDark = Color(0xFFF0DEDE)
val inverseOnSurfaceDark = Color(0xFF382E2E)
val inversePrimaryDark = Color(0xFF8F4A4D)
val surfaceDimDark = Color(0xFF1A1111)
val surfaceBrightDark = Color(0xFF413737)
val surfaceContainerLowestDark = Color(0xFF140C0C)
val surfaceContainerLowDark = Color(0xFF221919)
val surfaceContainerDark = Color(0xFF271D1D)
val surfaceContainerHighDark = Color(0xFF322828)
val surfaceContainerHighestDark = Color(0xFF3D3232)
@@ -0,0 +1,89 @@
package ru.shadowsparky.videobox.multiplatform.piggy
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
private val lightScheme = lightColorScheme(
primary = primaryLight,
onPrimary = onPrimaryLight,
primaryContainer = primaryContainerLight,
onPrimaryContainer = onPrimaryContainerLight,
secondary = secondaryLight,
onSecondary = onSecondaryLight,
secondaryContainer = secondaryContainerLight,
onSecondaryContainer = onSecondaryContainerLight,
tertiary = tertiaryLight,
onTertiary = onTertiaryLight,
tertiaryContainer = tertiaryContainerLight,
onTertiaryContainer = onTertiaryContainerLight,
error = errorLight,
onError = onErrorLight,
errorContainer = errorContainerLight,
onErrorContainer = onErrorContainerLight,
background = backgroundLight,
onBackground = onBackgroundLight,
surface = surfaceLight,
onSurface = onSurfaceLight,
surfaceVariant = surfaceVariantLight,
onSurfaceVariant = onSurfaceVariantLight,
outline = outlineLight,
outlineVariant = outlineVariantLight,
scrim = scrimLight,
inverseSurface = inverseSurfaceLight,
inverseOnSurface = inverseOnSurfaceLight,
inversePrimary = inversePrimaryLight,
surfaceDim = surfaceDimLight,
surfaceBright = surfaceBrightLight,
surfaceContainerLowest = surfaceContainerLowestLight,
surfaceContainerLow = surfaceContainerLowLight,
surfaceContainer = surfaceContainerLight,
surfaceContainerHigh = surfaceContainerHighLight,
surfaceContainerHighest = surfaceContainerHighestLight,
)
private val darkScheme = darkColorScheme(
primary = primaryDark,
onPrimary = onPrimaryDark,
primaryContainer = primaryContainerDark,
onPrimaryContainer = onPrimaryContainerDark,
secondary = secondaryDark,
onSecondary = onSecondaryDark,
secondaryContainer = secondaryContainerDark,
onSecondaryContainer = onSecondaryContainerDark,
tertiary = tertiaryDark,
onTertiary = onTertiaryDark,
tertiaryContainer = tertiaryContainerDark,
onTertiaryContainer = onTertiaryContainerDark,
error = errorDark,
onError = onErrorDark,
errorContainer = errorContainerDark,
onErrorContainer = onErrorContainerDark,
background = backgroundDark,
onBackground = onBackgroundDark,
surface = surfaceDark,
onSurface = onSurfaceDark,
surfaceVariant = surfaceVariantDark,
onSurfaceVariant = onSurfaceVariantDark,
outline = outlineDark,
outlineVariant = outlineVariantDark,
scrim = scrimDark,
inverseSurface = inverseSurfaceDark,
inverseOnSurface = inverseOnSurfaceDark,
inversePrimary = inversePrimaryDark,
surfaceDim = surfaceDimDark,
surfaceBright = surfaceBrightDark,
surfaceContainerLowest = surfaceContainerLowestDark,
surfaceContainerLow = surfaceContainerLowDark,
surfaceContainer = surfaceContainerDark,
surfaceContainerHigh = surfaceContainerHighDark,
surfaceContainerHighest = surfaceContainerHighestDark,
)
@Composable
fun getPiggyColorScheme(): ColorScheme {
return if (isSystemInDarkTheme()) darkScheme else lightScheme
}
@@ -39,7 +39,6 @@ fun AppBarWithSearchWithBackPreview() {
@Composable @Composable
fun InternalAppBarPreview() { fun InternalAppBarPreview() {
SearchAppBar( SearchAppBar(
Modifier,
TextFieldValue(""), TextFieldValue(""),
FocusRequester(), FocusRequester(),
{}, {},
@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="960"
android:viewportHeight="960">
<group android:scaleX="0.506"
android:scaleY="0.506"
android:translateX="237.12"
android:translateY="246.11555">
<path
android:pathData="M668.5,428.5Q680,417 680,400t-11.5,-28.5Q657,360 640,360t-28.5,11.5Q600,383 600,400t11.5,28.5Q623,440 640,440t28.5,-11.5ZM320,360h200v-80L320,280v80ZM180,840q-34,-114 -67,-227.5T80,380q0,-92 64,-156t156,-64h200q29,-38 70.5,-59t89.5,-21q25,0 42.5,17.5T720,140q0,6 -1.5,12t-3.5,11q-4,11 -7.5,22.5T702,209l91,91h87v279l-113,37 -67,224L480,840v-80h-80v80L180,840ZM240,760h80v-80h240v80h80l62,-206 98,-33v-141h-40L620,240q0,-20 2.5,-38.5T630,164q-29,8 -51,27.5T547,240L300,240q-58,0 -99,41t-41,99q0,98 27,191.5T240,760ZM480,462Z"
android:fillColor="#ffffff"/>
</group>
</vector>
@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="960"
android:viewportHeight="960">
<group android:scaleX="0.506"
android:scaleY="0.506"
android:translateX="237.12"
android:translateY="246.11555">
<path
android:pathData="M668.5,428.5Q680,417 680,400t-11.5,-28.5Q657,360 640,360t-28.5,11.5Q600,383 600,400t11.5,28.5Q623,440 640,440t28.5,-11.5ZM320,360h200v-80L320,280v80ZM180,840q-34,-114 -67,-227.5T80,380q0,-92 64,-156t156,-64h200q29,-38 70.5,-59t89.5,-21q25,0 42.5,17.5T720,140q0,6 -1.5,12t-3.5,11q-4,11 -7.5,22.5T702,209l91,91h87v279l-113,37 -67,224L480,840v-80h-80v80L180,840ZM240,760h80v-80h240v80h80l62,-206 98,-33v-141h-40L620,240q0,-20 2.5,-38.5T630,164q-29,8 -51,27.5T547,240L300,240q-58,0 -99,41t-41,99q0,98 27,191.5T240,760ZM480,462Z"
android:fillColor="#ffffff"/>
</group>
</vector>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/pig_launcher_background"/>
<foreground android:drawable="@drawable/pig_launcher_foreground"/>
<monochrome android:drawable="@drawable/pig_launcher_monochrome"/>
</adaptive-icon>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="pig_launcher_background">#FDD5D5</color>
</resources>
@@ -1,5 +1,7 @@
<resources> <resources>
<string name="app_name">Videobox</string> <string name="app_name">Videobox</string>
<string name="app_name_pig">Свиное Рыло</string>
<string name="saved_movies">Сохранённые фильмы</string> <string name="saved_movies">Сохранённые фильмы</string>
<string name="saved_movies_long">Просмотр списка ранее сохранённых фильмов</string> <string name="saved_movies_long">Просмотр списка ранее сохранённых фильмов</string>
</resources> </resources>
@@ -9,6 +9,7 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.seiko.imageloader.LocalImageLoader import com.seiko.imageloader.LocalImageLoader
@@ -30,9 +31,11 @@ import ru.shadowsparky.vbox.shared.presentation.settings.register.RegisterScreen
import ru.shadowsparky.vbox.shared.presentation.settings.serv.EditServScreen import ru.shadowsparky.vbox.shared.presentation.settings.serv.EditServScreen
import ru.shadowsparky.vbox.shared.presentation.settings.tag.TagScreen import ru.shadowsparky.vbox.shared.presentation.settings.tag.TagScreen
val LocalColorSchemeProvider = compositionLocalOf<ColorScheme?> { null }
@Composable @Composable
fun getColorScheme(): ColorScheme { fun getColorScheme(): ColorScheme {
return ru.shadowsparky.ui.theme.getColorScheme() return LocalColorSchemeProvider.current ?: ru.shadowsparky.ui.theme.getColorScheme()
} }
@Composable @Composable
@@ -92,7 +92,6 @@ fun SearchScreen(
BaseMainScreen( BaseMainScreen(
appBar = { appBar = {
SearchAppBar( SearchAppBar(
modifier = Modifier.fillMaxWidth(),
textFieldValue = textFieldValue, textFieldValue = textFieldValue,
onDone = { component.done(textFieldValue.text) }, onDone = { component.done(textFieldValue.text) },
onValueChange = { textFieldValue = it }, onValueChange = { textFieldValue = it },
@@ -214,7 +213,6 @@ expect fun Modifier.requestFocusOnDownEvent(focusRequester: FocusRequester): Mod
@Composable @Composable
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
fun SearchAppBar( fun SearchAppBar(
modifier: Modifier,
textFieldValue: TextFieldValue, textFieldValue: TextFieldValue,
focusRequester: FocusRequester, focusRequester: FocusRequester,
onDone: () -> Unit, onDone: () -> Unit,
@@ -230,7 +228,7 @@ fun SearchAppBar(
} }
val blur = LocalBlurState.current val blur = LocalBlurState.current
AppBarWithSearch( AppBarWithSearch(
modifier = modifier.applyBlurEffect(blur), modifier = Modifier.applyBlurEffect(blur),
state = rememberSearchBarState(), state = rememberSearchBarState(),
colors = ComponentDefaults.appBarWithSearchDefaults(), colors = ComponentDefaults.appBarWithSearchDefaults(),
inputField = { inputField = {
@@ -316,10 +314,7 @@ fun AppBarWithSearch(
BackButton { onBack() } BackButton { onBack() }
Spacer(Modifier.width(12.dp)) Spacer(Modifier.width(12.dp))
} }
Text( Text(text = title, maxLines = 1)
text = title,
maxLines = 1
)
} }
} }
} }