feat: AI & showcase
This commit is contained in:
@@ -113,7 +113,6 @@ android {
|
||||
compose = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
storeFile = File(System.getProperty("user.home"), ".android/purrfectsnap-release.keystore")
|
||||
@@ -359,6 +358,7 @@ afterEvaluate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
properties["debug_flavor"]?.let {
|
||||
|
||||
@@ -113,6 +113,22 @@ class AppDatabase(
|
||||
"id CHAR(36) PRIMARY KEY",
|
||||
"content TEXT",
|
||||
),
|
||||
"assistant_registry" to listOf(
|
||||
"id VARCHAR PRIMARY KEY",
|
||||
"kind VARCHAR",
|
||||
"title VARCHAR",
|
||||
"category VARCHAR",
|
||||
"path TEXT",
|
||||
"description TEXT",
|
||||
"settingKey VARCHAR",
|
||||
"screenRoute VARCHAR",
|
||||
"allowedActions TEXT",
|
||||
"allowedValues TEXT",
|
||||
"aliases TEXT",
|
||||
"commonTypos TEXT",
|
||||
"examples TEXT",
|
||||
"searchTokens TEXT",
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package me.eternal.purrfectsnap.storage
|
||||
|
||||
import android.content.ContentValues
|
||||
import me.eternal.purrfectsnap.common.util.ktx.getStringOrNull
|
||||
import org.json.JSONArray
|
||||
|
||||
data class AssistantRegistryEntry(
|
||||
val id: String,
|
||||
val kind: String,
|
||||
val title: String,
|
||||
val category: String,
|
||||
val path: String,
|
||||
val description: String,
|
||||
val settingKey: String? = null,
|
||||
val screenRoute: String? = null,
|
||||
val allowedActions: List<String> = emptyList(),
|
||||
val allowedValues: List<String> = emptyList(),
|
||||
val aliases: List<String> = emptyList(),
|
||||
val commonTypos: List<String> = emptyList(),
|
||||
val examples: List<String> = emptyList(),
|
||||
val searchTokens: List<String> = emptyList()
|
||||
)
|
||||
|
||||
private fun List<String>.toJsonArrayString(): String = JSONArray(this).toString()
|
||||
|
||||
private fun parseStringList(raw: String?): List<String> {
|
||||
if (raw.isNullOrBlank()) return emptyList()
|
||||
return runCatching {
|
||||
val array = JSONArray(raw)
|
||||
buildList {
|
||||
for (index in 0 until array.length()) {
|
||||
array.optString(index).takeIf { it.isNotBlank() }?.let(::add)
|
||||
}
|
||||
}
|
||||
}.getOrDefault(emptyList())
|
||||
}
|
||||
|
||||
fun AppDatabase.replaceAssistantRegistry(entries: List<AssistantRegistryEntry>) {
|
||||
database.beginTransaction()
|
||||
try {
|
||||
database.execSQL("DELETE FROM assistant_registry")
|
||||
entries.forEach { entry ->
|
||||
database.insert(
|
||||
"assistant_registry",
|
||||
null,
|
||||
ContentValues().apply {
|
||||
put("id", entry.id)
|
||||
put("kind", entry.kind)
|
||||
put("title", entry.title)
|
||||
put("category", entry.category)
|
||||
put("path", entry.path)
|
||||
put("description", entry.description)
|
||||
put("settingKey", entry.settingKey)
|
||||
put("screenRoute", entry.screenRoute)
|
||||
put("allowedActions", entry.allowedActions.toJsonArrayString())
|
||||
put("allowedValues", entry.allowedValues.toJsonArrayString())
|
||||
put("aliases", entry.aliases.toJsonArrayString())
|
||||
put("commonTypos", entry.commonTypos.toJsonArrayString())
|
||||
put("examples", entry.examples.toJsonArrayString())
|
||||
put("searchTokens", entry.searchTokens.toJsonArrayString())
|
||||
}
|
||||
)
|
||||
}
|
||||
database.setTransactionSuccessful()
|
||||
} finally {
|
||||
database.endTransaction()
|
||||
}
|
||||
}
|
||||
|
||||
fun AppDatabase.getAssistantRegistryEntries(): List<AssistantRegistryEntry> {
|
||||
return database.rawQuery("SELECT * FROM assistant_registry", null).use { cursor ->
|
||||
val entries = mutableListOf<AssistantRegistryEntry>()
|
||||
while (cursor.moveToNext()) {
|
||||
entries += AssistantRegistryEntry(
|
||||
id = cursor.getStringOrNull("id") ?: continue,
|
||||
kind = cursor.getStringOrNull("kind") ?: "feature",
|
||||
title = cursor.getStringOrNull("title") ?: "",
|
||||
category = cursor.getStringOrNull("category") ?: "",
|
||||
path = cursor.getStringOrNull("path") ?: "",
|
||||
description = cursor.getStringOrNull("description") ?: "",
|
||||
settingKey = cursor.getStringOrNull("settingKey"),
|
||||
screenRoute = cursor.getStringOrNull("screenRoute"),
|
||||
allowedActions = parseStringList(cursor.getStringOrNull("allowedActions")),
|
||||
allowedValues = parseStringList(cursor.getStringOrNull("allowedValues")),
|
||||
aliases = parseStringList(cursor.getStringOrNull("aliases")),
|
||||
commonTypos = parseStringList(cursor.getStringOrNull("commonTypos")),
|
||||
examples = parseStringList(cursor.getStringOrNull("examples")),
|
||||
searchTokens = parseStringList(cursor.getStringOrNull("searchTokens"))
|
||||
)
|
||||
}
|
||||
entries
|
||||
}
|
||||
}
|
||||
2420
app/src/main/kotlin/me/eternal/purrfectsnap/ui/manager/AI.kt
Normal file
2420
app/src/main/kotlin/me/eternal/purrfectsnap/ui/manager/AI.kt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -269,7 +269,7 @@ fun FloatingTopBar(
|
||||
if (onBack != null) {
|
||||
translationX = morphingParams.horizontalShift.toPx()
|
||||
}
|
||||
},
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
|
||||
@@ -32,7 +32,6 @@ import me.eternal.purrfectsnap.R
|
||||
import me.eternal.purrfectsnap.ui.manager.Routes
|
||||
import me.eternal.purrfectsnap.ui.manager.ManagerTheme
|
||||
import me.eternal.purrfectsnap.ui.manager.theme.PurrfectPalette
|
||||
import me.eternal.purrfectsnap.ui.util.PurrfectMarqueeText
|
||||
import me.eternal.purrfectsnap.ui.util.scaleOnPress
|
||||
|
||||
class HomeAbout : Routes.Route() {
|
||||
@@ -65,6 +64,7 @@ class HomeAbout : Routes.Route() {
|
||||
name: String,
|
||||
imageRes: Int,
|
||||
avenirNext: FontFamily,
|
||||
subtitle: String? = null,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val tapSource = remember { MutableInteractionSource() }
|
||||
@@ -85,7 +85,9 @@ class HomeAbout : Routes.Route() {
|
||||
routes.retroGame.navigate()
|
||||
}
|
||||
},
|
||||
modifier = modifier.scaleOnPress(tapSource),
|
||||
modifier = modifier
|
||||
.height(150.dp)
|
||||
.scaleOnPress(tapSource),
|
||||
interactionSource = tapSource,
|
||||
shape = RoundedCornerShape(22.dp),
|
||||
color = Color.White.copy(alpha = 0.06f),
|
||||
@@ -94,9 +96,11 @@ class HomeAbout : Routes.Route() {
|
||||
shadowElevation = 0.dp
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(14.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(14.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.size(64.dp),
|
||||
@@ -111,15 +115,28 @@ class HomeAbout : Routes.Route() {
|
||||
modifier = Modifier.fillMaxSize().clip(CircleShape)
|
||||
)
|
||||
}
|
||||
PurrfectMarqueeText(
|
||||
Text(
|
||||
text = name,
|
||||
color = Color.White,
|
||||
style = TextStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 16.sp,
|
||||
fontFamily = avenirNext
|
||||
)
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 16.sp,
|
||||
fontFamily = avenirNext,
|
||||
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
subtitle?.takeIf { it.isNotBlank() }?.let {
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
text = it,
|
||||
color = PurrfectPalette.textSecondary,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +127,8 @@ import me.eternal.purrfectsnap.storage.getQuickTiles
|
||||
import me.eternal.purrfectsnap.storage.setQuickTiles
|
||||
import me.eternal.purrfectsnap.ui.manager.Routes
|
||||
import me.eternal.purrfectsnap.ui.manager.ManagerTheme
|
||||
import me.eternal.purrfectsnap.ui.manager.ManagerAssistantEntry
|
||||
import me.eternal.purrfectsnap.ui.manager.ManagerAssistantTriggerStyle
|
||||
import me.eternal.purrfectsnap.ui.manager.theme.PurrfectPalette
|
||||
import me.eternal.purrfectsnap.ui.manager.data.UpdateDownloader
|
||||
import me.eternal.purrfectsnap.ui.manager.data.Updater
|
||||
@@ -271,27 +273,31 @@ class HomeRootSection : Routes.Route() {
|
||||
icon: ImageVector,
|
||||
label: String? = null,
|
||||
contentDescription: String? = label,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier.height(36.dp),
|
||||
shape = RoundedCornerShape(40),
|
||||
color = Color.White.copy(alpha = 0.06f),
|
||||
border = BorderStroke(1.dp, Color.White.copy(alpha = 0.12f))
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(40))
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 14.dp, vertical = 8.dp),
|
||||
.padding(horizontal = 10.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(icon, contentDescription = contentDescription, tint = Color.White)
|
||||
Icon(icon, contentDescription = contentDescription, tint = Color.White, modifier = Modifier.size(20.dp))
|
||||
label?.let {
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(
|
||||
text = it,
|
||||
color = Color.White,
|
||||
fontSize = 13.sp,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
@@ -323,13 +329,20 @@ class HomeRootSection : Routes.Route() {
|
||||
|
||||
@Composable
|
||||
private fun RowScope.HomeActionChips() {
|
||||
ManagerAssistantEntry(
|
||||
context = context,
|
||||
routes = routes,
|
||||
style = ManagerAssistantTriggerStyle.DEFAULT
|
||||
)
|
||||
TopBarActionChip(
|
||||
icon = Icons.Filled.BugReport,
|
||||
label = context.translation["manager.routes.home_logs"]
|
||||
label = context.translation["manager.routes.home_logs"],
|
||||
modifier = Modifier
|
||||
) { routes.homeLogs.navigate() }
|
||||
TopBarActionChip(
|
||||
icon = Icons.Filled.Info,
|
||||
label = translation["manager.routes.home_about"]
|
||||
label = translation["manager.routes.home_about"],
|
||||
modifier = Modifier
|
||||
) { routes.about.navigate() }
|
||||
}
|
||||
|
||||
@@ -733,9 +746,8 @@ class HomeRootSection : Routes.Route() {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.wrapContentWidth(),
|
||||
) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
HomeActionChips()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,13 +135,26 @@ fun HomeAbout.AphelionAboutScreen(nav: NavBackStackEntry) {
|
||||
color = Color.White,
|
||||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
)
|
||||
Row(
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
DeveloperCard(name = "ΞTΞRNAL", imageRes = R.drawable.pfp_external, avenirNext = avenirNext, modifier = Modifier.weight(1f))
|
||||
DeveloperCard(name = "<RSR/>", imageRes = R.drawable.pfp_rsr, avenirNext = avenirNext, modifier = Modifier.weight(1f))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
DeveloperCard(name = "Eternal", subtitle = "", imageRes = R.drawable.pfp_external, avenirNext = avenirNext, modifier = Modifier.weight(1f))
|
||||
DeveloperCard(name = "Kaladin", subtitle = "", imageRes = R.drawable.pfp_kaladin, avenirNext = avenirNext, modifier = Modifier.weight(1f))
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
DeveloperCard(name = "schrodingerspet", subtitle = "", imageRes = R.drawable.pfp_schrodingerspet, avenirNext = avenirNext, modifier = Modifier.weight(1f))
|
||||
DeveloperCard(name = "RSR", subtitle = "", imageRes = R.drawable.pfp_rsr, avenirNext = avenirNext, modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.lerp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.zIndex
|
||||
@@ -63,6 +64,8 @@ import me.eternal.purrfectsnap.ui.manager.components.AestheticDialog
|
||||
import me.eternal.purrfectsnap.ui.manager.data.UpdateDownloader
|
||||
import me.eternal.purrfectsnap.ui.manager.data.Updater
|
||||
import me.eternal.purrfectsnap.ui.manager.data.Updater.Channel
|
||||
import me.eternal.purrfectsnap.ui.manager.ManagerAssistantEntry
|
||||
import me.eternal.purrfectsnap.ui.manager.ManagerAssistantTriggerStyle
|
||||
import me.eternal.purrfectsnap.ui.manager.pages.home.HomeRootSection
|
||||
import me.eternal.purrfectsnap.ui.manager.pages.home.QuickActionsDialog
|
||||
import me.eternal.purrfectsnap.ui.manager.theme.PurrfectPalette
|
||||
@@ -148,11 +151,15 @@ fun HomeRootSection.AphelionHomeScreen(nav: NavBackStackEntry) {
|
||||
label: String? = null,
|
||||
contentDescription: String? = label,
|
||||
shrinkFactor: Float = 1f,
|
||||
modifier: Modifier = Modifier,
|
||||
expandedWidth: Dp? = null,
|
||||
collapsedWidth: Dp = 36.dp,
|
||||
haptic: HapticFeedback,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val targetWidth = expandedWidth?.let { lerp(collapsedWidth, it, shrinkFactor) }
|
||||
Surface(
|
||||
modifier = Modifier.height(36.dp).widthIn(min = 36.dp),
|
||||
modifier = modifier.height(36.dp).then(if (targetWidth != null) Modifier.width(targetWidth) else Modifier),
|
||||
shape = RoundedCornerShape(40),
|
||||
color = Color.White.copy(alpha = 0.06f),
|
||||
border = BorderStroke(
|
||||
@@ -167,9 +174,10 @@ fun HomeRootSection.AphelionHomeScreen(nav: NavBackStackEntry) {
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(40))
|
||||
.clickable { haptic.performHapticFeedback(HapticFeedbackType.LongPress); onClick() }
|
||||
.padding(vertical = 6.dp, horizontal = lerp(10.dp, 12.dp, shrinkFactor)),
|
||||
.padding(vertical = 6.dp, horizontal = lerp(7.dp, 10.dp, shrinkFactor)),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
@@ -182,17 +190,19 @@ fun HomeRootSection.AphelionHomeScreen(nav: NavBackStackEntry) {
|
||||
}
|
||||
)
|
||||
if (label != null) {
|
||||
val labelAlpha = (shrinkFactor - 0.1f).coerceIn(0f, 1f)
|
||||
Spacer(modifier = Modifier.width((8 * shrinkFactor).dp))
|
||||
Text(
|
||||
text = label,
|
||||
color = Color.White.copy(alpha = labelAlpha),
|
||||
fontSize = 12.sp, fontWeight = FontWeight.Medium,
|
||||
maxLines = 1, overflow = TextOverflow.Clip,
|
||||
modifier = Modifier
|
||||
.graphicsLayer { alpha = labelAlpha; translationX = (-4 * (1f - shrinkFactor)).dp.toPx() }
|
||||
.widthIn(max = (75 * shrinkFactor).dp)
|
||||
)
|
||||
val labelAlpha = ((shrinkFactor - 0.45f) / 0.55f).coerceIn(0f, 1f)
|
||||
if (labelAlpha > 0.02f) {
|
||||
Spacer(modifier = Modifier.width((8 * shrinkFactor).dp))
|
||||
Text(
|
||||
text = label,
|
||||
color = Color.White.copy(alpha = labelAlpha),
|
||||
fontSize = 12.sp, fontWeight = FontWeight.Medium,
|
||||
maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier
|
||||
.graphicsLayer { alpha = labelAlpha; translationX = (-4 * (1f - shrinkFactor)).dp.toPx() }
|
||||
.weight(1f, fill = false)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,14 +216,23 @@ fun HomeRootSection.AphelionHomeScreen(nav: NavBackStackEntry) {
|
||||
val shrinkFactor by remember(scrollState.value) {
|
||||
derivedStateOf { (1f - (scrollState.value.toFloat() / Motion.HEADER_MORPH_THRESHOLD)).coerceIn(0f, 1f) }
|
||||
}
|
||||
ManagerAssistantEntry(
|
||||
context = context,
|
||||
routes = routes,
|
||||
style = ManagerAssistantTriggerStyle.APHELION,
|
||||
shrinkFactor = shrinkFactor,
|
||||
modifier = Modifier.width(lerp(36.dp, 66.dp, shrinkFactor))
|
||||
)
|
||||
AphelionTopBarActionChip(
|
||||
icon = Icons.Filled.BugReport,
|
||||
label = context.translation["manager.routes.home_logs"],
|
||||
expandedWidth = 88.dp,
|
||||
shrinkFactor = shrinkFactor, haptic = haptic
|
||||
) { routes.homeLogs.navigate() }
|
||||
AphelionTopBarActionChip(
|
||||
icon = Icons.Filled.Settings,
|
||||
label = context.translation["manager.routes.home_settings"],
|
||||
expandedWidth = 96.dp,
|
||||
shrinkFactor = shrinkFactor, haptic = haptic
|
||||
) { routes.settings.navigate() }
|
||||
}
|
||||
@@ -646,37 +665,27 @@ fun HomeRootSection.AphelionHomeScreen(nav: NavBackStackEntry) {
|
||||
.padding(horizontal = 16.dp)
|
||||
.height(headerHeight)
|
||||
) {
|
||||
Text(
|
||||
text = "PurrfectSnap",
|
||||
color = Color.White.copy(alpha = stickyBrandingAlpha),
|
||||
fontSize = 18.sp, fontWeight = FontWeight.Bold, fontFamily = avenirNext,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
val announcementShift by remember(focusFactor) { derivedStateOf { (-6 * focusFactor).dp } }
|
||||
Row(
|
||||
modifier = Modifier.align(Alignment.CenterStart).graphicsLayer { translationX = announcementShift.toPx() },
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.wrapContentWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
AphelionTopBarActionChip(
|
||||
icon = Icons.Filled.Notifications, label = null,
|
||||
expandedWidth = 52.dp,
|
||||
shrinkFactor = (1f - focusFactor).coerceIn(0f, 1f),
|
||||
contentDescription = translation["announcements_button_description"],
|
||||
haptic = haptic
|
||||
) { showAnnouncementsDialog = true; loadAnnouncements() }
|
||||
AphelionTopBarActionChip(
|
||||
icon = Icons.Filled.Description, label = null,
|
||||
expandedWidth = 52.dp,
|
||||
shrinkFactor = (1f - focusFactor).coerceIn(0f, 1f),
|
||||
contentDescription = translation.getOrNull("changelog_button_description") ?: "Open full changelog",
|
||||
haptic = haptic
|
||||
) { showFullChangelogDialog = true; loadFullChangelog() }
|
||||
}
|
||||
val settingsShift by remember(focusFactor) { derivedStateOf { (6 * focusFactor).dp } }
|
||||
Row(
|
||||
modifier = Modifier.align(Alignment.CenterEnd).graphicsLayer { translationX = settingsShift.toPx() },
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AphelionHomeActionChips(scrollState = scrollState, haptic = haptic)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,8 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.eternal.purrfectsnap.LogLine
|
||||
import me.eternal.purrfectsnap.ui.manager.ManagerAssistantEntry
|
||||
import me.eternal.purrfectsnap.ui.manager.ManagerAssistantTriggerStyle
|
||||
import me.eternal.purrfectsnap.LogReader
|
||||
import me.eternal.purrfectsnap.R
|
||||
import me.eternal.purrfectsnap.action.EnumQuickActions
|
||||
@@ -154,24 +156,28 @@ object LegacyTheme : ThemeContract {
|
||||
icon: ImageVector,
|
||||
label: String? = null,
|
||||
contentDescription: String? = label,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier.height(36.dp),
|
||||
shape = RoundedCornerShape(40),
|
||||
color = Color.White.copy(alpha = 0.06f),
|
||||
border = BorderStroke(1.dp, Color.White.copy(alpha = 0.12f))
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(40))
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 14.dp, vertical = 8.dp),
|
||||
.padding(horizontal = 10.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(icon, contentDescription = contentDescription, tint = Color.White)
|
||||
Icon(icon, contentDescription = contentDescription, tint = Color.White, modifier = Modifier.size(20.dp))
|
||||
label?.let {
|
||||
Text(text = it, color = Color.White, fontSize = 13.sp, fontWeight = FontWeight.Medium, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(text = it, color = Color.White, fontSize = 12.sp, fontWeight = FontWeight.Medium, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,8 +185,22 @@ object LegacyTheme : ThemeContract {
|
||||
|
||||
@Composable
|
||||
fun RowScope.LocalHomeActionChips() {
|
||||
LocalTopBarActionChip(icon = Icons.Filled.BugReport, label = context.translation["manager.routes.home_logs"]) { routes.homeLogs.navigate() }
|
||||
LocalTopBarActionChip(icon = Icons.Filled.Info, label = translation["manager.routes.home_about"]) { routes.about.navigate() }
|
||||
ManagerAssistantEntry(
|
||||
context = context,
|
||||
routes = routes,
|
||||
style = ManagerAssistantTriggerStyle.DEFAULT,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
LocalTopBarActionChip(
|
||||
icon = Icons.Filled.BugReport,
|
||||
label = context.translation["manager.routes.home_logs"],
|
||||
modifier = Modifier.weight(1f)
|
||||
) { routes.homeLogs.navigate() }
|
||||
LocalTopBarActionChip(
|
||||
icon = Icons.Filled.Info,
|
||||
label = translation["manager.routes.home_about"],
|
||||
modifier = Modifier.weight(1f)
|
||||
) { routes.about.navigate() }
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -478,18 +498,26 @@ object LegacyTheme : ThemeContract {
|
||||
Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState).padding(bottom = contentBottomPadding)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(WindowInsets.statusBars.asPaddingValues()).padding(horizontal = cardMargin, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
LocalTopBarActionChip(icon = Icons.Filled.Notifications, label = null, contentDescription = translation["announcements_button_description"]) {
|
||||
showAnnouncementsDialog = true; loadAnnouncements()
|
||||
}
|
||||
LocalTopBarActionChip(icon = Icons.Filled.Description, label = null, contentDescription = translation.getOrNull("changelog_button_description") ?: "Open full changelog") {
|
||||
showFullChangelogDialog = true; loadFullChangelog(changelogUrl)
|
||||
}
|
||||
}
|
||||
Row(modifier = Modifier.wrapContentWidth(Alignment.End), horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
LocalTopBarActionChip(
|
||||
icon = Icons.Filled.Notifications,
|
||||
label = null,
|
||||
modifier = Modifier.width(56.dp),
|
||||
contentDescription = translation["announcements_button_description"]
|
||||
) { showAnnouncementsDialog = true; loadAnnouncements() }
|
||||
LocalTopBarActionChip(
|
||||
icon = Icons.Filled.Description,
|
||||
label = null,
|
||||
modifier = Modifier.width(56.dp),
|
||||
contentDescription = translation.getOrNull("changelog_button_description") ?: "Open full changelog"
|
||||
) { showFullChangelogDialog = true; loadFullChangelog(changelogUrl) }
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
LocalHomeActionChips()
|
||||
}
|
||||
}
|
||||
@@ -1720,9 +1748,15 @@ object LegacyTheme : ThemeContract {
|
||||
)
|
||||
Text(text = translation["about_tagline"] ?: "", fontSize = 13.sp, color = Color(0xFFD9D3FF), textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth())
|
||||
Text(text = translation["about_lead_developers_title"] ?: "Lead Developers", fontSize = 15.sp, fontWeight = FontWeight.SemiBold, color = Color.White, modifier = Modifier.padding(top = 10.dp))
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally), verticalAlignment = Alignment.CenterVertically) {
|
||||
DeveloperCard(name = "ΞTΞRNAL", imageRes = R.drawable.pfp_external, avenirNext = avenirNext, modifier = Modifier.weight(1f))
|
||||
DeveloperCard(name = "<RSR/>", imageRes = R.drawable.pfp_rsr, avenirNext = avenirNext, modifier = Modifier.weight(1f))
|
||||
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally), verticalAlignment = Alignment.CenterVertically) {
|
||||
DeveloperCard(name = "Eternal", subtitle = "", imageRes = R.drawable.pfp_external, avenirNext = avenirNext, modifier = Modifier.weight(1f))
|
||||
DeveloperCard(name = "Kaladin", subtitle = "", imageRes = R.drawable.pfp_kaladin, avenirNext = avenirNext, modifier = Modifier.weight(1f))
|
||||
}
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally), verticalAlignment = Alignment.CenterVertically) {
|
||||
DeveloperCard(name = "schrodingerspet", subtitle = "", imageRes = R.drawable.pfp_schrodingerspet, avenirNext = avenirNext, modifier = Modifier.weight(1f))
|
||||
DeveloperCard(name = "RSR", subtitle = "", imageRes = R.drawable.pfp_rsr, avenirNext = avenirNext, modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1784,11 +1818,14 @@ object LegacyTheme : ThemeContract {
|
||||
name: String,
|
||||
imageRes: Int,
|
||||
avenirNext: FontFamily,
|
||||
subtitle: String? = null,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val tapSource = remember { MutableInteractionSource() }
|
||||
Surface(
|
||||
modifier = modifier.scaleOnPress(tapSource),
|
||||
modifier = modifier
|
||||
.height(150.dp)
|
||||
.scaleOnPress(tapSource),
|
||||
shape = RoundedCornerShape(22.dp),
|
||||
color = Color.White.copy(alpha = 0.06f),
|
||||
border = BorderStroke(1.dp, Color.White.copy(alpha = 0.1f)),
|
||||
@@ -1796,9 +1833,11 @@ object LegacyTheme : ThemeContract {
|
||||
shadowElevation = 0.dp
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(14.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(14.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.size(64.dp),
|
||||
@@ -1813,15 +1852,28 @@ object LegacyTheme : ThemeContract {
|
||||
modifier = Modifier.fillMaxSize().clip(CircleShape)
|
||||
)
|
||||
}
|
||||
PurrfectMarqueeText(
|
||||
Text(
|
||||
text = name,
|
||||
color = Color.White,
|
||||
style = TextStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 16.sp,
|
||||
fontFamily = avenirNext
|
||||
)
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 16.sp,
|
||||
fontFamily = avenirNext,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
subtitle?.takeIf { it.isNotBlank() }?.let {
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
text = it,
|
||||
color = Color(0xFFD9D3FF),
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.Flag
|
||||
import androidx.compose.material.icons.filled.Folder
|
||||
import androidx.compose.material.icons.filled.Language
|
||||
import androidx.compose.material.icons.filled.SmartToy
|
||||
import androidx.compose.material.icons.filled.VerifiedUser
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -93,6 +94,8 @@ import androidx.navigation.compose.rememberNavController
|
||||
import me.eternal.purrfectsnap.RemoteSideContext
|
||||
import me.eternal.purrfectsnap.SharedContextHolder
|
||||
import me.eternal.purrfectsnap.common.ui.AppMaterialTheme
|
||||
import me.eternal.purrfectsnap.ui.manager.ManagerAssistantDialog
|
||||
import me.eternal.purrfectsnap.ui.manager.Routes
|
||||
import me.eternal.purrfectsnap.ui.manager.components.AestheticDialog
|
||||
import me.eternal.purrfectsnap.ui.manager.theme.PurrfectPalette
|
||||
import me.eternal.purrfectsnap.ui.setup.screens.SetupScreen
|
||||
@@ -104,6 +107,8 @@ import me.eternal.purrfectsnap.ui.setup.screens.impl.PickLanguageScreen
|
||||
import me.eternal.purrfectsnap.ui.setup.screens.impl.PatchSnapchatScreen
|
||||
import me.eternal.purrfectsnap.ui.setup.screens.impl.RootInstallSnapchatScreen
|
||||
import me.eternal.purrfectsnap.ui.setup.screens.impl.SaveFolderScreen
|
||||
import me.eternal.purrfectsnap.ui.setup.screens.impl.IntroShowcaseScreen
|
||||
import me.eternal.purrfectsnap.ui.util.ActivityLauncherHelper
|
||||
import me.eternal.purrfectsnap.ui.util.scaleOnPress
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@@ -127,6 +132,9 @@ class SetupActivity : ComponentActivity() {
|
||||
}
|
||||
val requirements = intent.getIntExtra("requirements", Requirements.FIRST_RUN)
|
||||
val setupPrefs = setupContext.sharedPreferences
|
||||
val setupRoutes = Routes(setupContext).apply {
|
||||
activityLauncher = ActivityLauncherHelper(this@SetupActivity)
|
||||
}
|
||||
fun hasRequirement(requirement: Int) = requirements and requirement == requirement
|
||||
val wasInProgress = setupPrefs.getBoolean("setup_in_progress", false)
|
||||
val isFirstRunFlow = hasRequirement(Requirements.FIRST_RUN) || wasInProgress
|
||||
@@ -159,6 +167,7 @@ class SetupActivity : ComponentActivity() {
|
||||
|
||||
val requiredScreens = mutableListOf<SetupScreen>().apply {
|
||||
if (isFirstRunFlow || hasRequirement(Requirements.LANGUAGE)) {
|
||||
add(IntroShowcaseScreen().apply { route = "introShowcase" })
|
||||
add(PickLanguageScreen().apply { route = "language" })
|
||||
if (isFirstRunFlow) {
|
||||
add(InstallModeScreen(
|
||||
@@ -315,19 +324,7 @@ class SetupActivity : ComponentActivity() {
|
||||
AppMaterialTheme {
|
||||
val view = LocalView.current
|
||||
val navBarPadding = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
||||
var showImportantDialog by rememberSaveable {
|
||||
mutableStateOf(!setupPrefs.getBoolean("setup_important_notice_shown", false))
|
||||
}
|
||||
var importantTimeout by remember { mutableIntStateOf(5) }
|
||||
LaunchedEffect(showImportantDialog) {
|
||||
if (showImportantDialog) {
|
||||
importantTimeout = 5
|
||||
while (importantTimeout > 0) {
|
||||
delay(1000)
|
||||
importantTimeout--
|
||||
}
|
||||
}
|
||||
}
|
||||
var setupAiPrompt by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
@@ -344,46 +341,8 @@ class SetupActivity : ComponentActivity() {
|
||||
.fillMaxSize()
|
||||
.background(Color.Transparent)
|
||||
) {
|
||||
if (showImportantDialog) {
|
||||
val confirmLabel = if (importantTimeout > 0) {
|
||||
translation.format(
|
||||
"setup.activity.important_confirm_timeout",
|
||||
"seconds" to importantTimeout.toString()
|
||||
)
|
||||
} else {
|
||||
translation["setup.activity.important_confirm"]
|
||||
}
|
||||
AestheticDialog(
|
||||
onDismissRequest = {
|
||||
if (importantTimeout == 0) {
|
||||
showImportantDialog = false
|
||||
setupPrefs.edit().putBoolean("setup_important_notice_shown", true).apply()
|
||||
}
|
||||
},
|
||||
title = translation["setup.activity.important_title"],
|
||||
text = "",
|
||||
icon = Icons.Filled.Warning,
|
||||
confirmButtonText = confirmLabel,
|
||||
onConfirm = {
|
||||
if (importantTimeout == 0) {
|
||||
showImportantDialog = false
|
||||
setupPrefs.edit().putBoolean("setup_important_notice_shown", true).apply()
|
||||
}
|
||||
},
|
||||
confirmEnabled = importantTimeout == 0,
|
||||
showCloseButton = false,
|
||||
customContent = {
|
||||
Text(
|
||||
text = translation["setup.activity.important_message"],
|
||||
color = PurrfectPalette.textSecondary,
|
||||
lineHeight = 18.sp
|
||||
)
|
||||
},
|
||||
opaque = true
|
||||
)
|
||||
}
|
||||
SetupAuroraBackground()
|
||||
SetupTopBar()
|
||||
SetupTopBar(onAskAi = { setupAiPrompt = "hi" })
|
||||
val bottomPadding = 118.dp + navBarPadding
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -468,6 +427,14 @@ class SetupActivity : ComponentActivity() {
|
||||
.navigationBarsPadding()
|
||||
.padding(bottom = 32.dp)
|
||||
)
|
||||
setupAiPrompt?.let { prompt ->
|
||||
ManagerAssistantDialog(
|
||||
context = setupContext,
|
||||
routes = setupRoutes,
|
||||
initialUserMessage = prompt,
|
||||
onDismiss = { setupAiPrompt = null }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -483,6 +450,12 @@ private fun SetupScreen.meta(context: RemoteSideContext): SetupStepMeta {
|
||||
subtitle = translation["setup.activity.language_subtitle"],
|
||||
icon = Icons.Filled.Language
|
||||
)
|
||||
is IntroShowcaseScreen -> SetupStepMeta(
|
||||
route = route,
|
||||
title = "Welcome",
|
||||
subtitle = "Preview what PurrfectSnap can do",
|
||||
icon = Icons.Filled.AutoAwesome
|
||||
)
|
||||
|
||||
is InstallModeScreen -> SetupStepMeta(
|
||||
route = route,
|
||||
@@ -587,7 +560,7 @@ private fun SetupAuroraBackground() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SetupTopBar() {
|
||||
private fun SetupTopBar(onAskAi: () -> Unit) {
|
||||
val topPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
@@ -613,14 +586,37 @@ private fun SetupTopBar() {
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 18.dp, vertical = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "PurrfectSnap",
|
||||
color = PurrfectPalette.textPrimary,
|
||||
fontWeight = FontWeight.ExtraBold,
|
||||
fontSize = 18.sp
|
||||
fontSize = 18.sp,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Surface(
|
||||
shape = RoundedCornerShape(40),
|
||||
color = Color.White.copy(alpha = 0.08f),
|
||||
border = androidx.compose.foundation.BorderStroke(1.dp, Color.White.copy(alpha = 0.14f))
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(40))
|
||||
.clickable(onClick = onAskAi)
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Icon(Icons.Filled.SmartToy, contentDescription = null, tint = Color.White)
|
||||
Text(
|
||||
text = "Ask AI",
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 13.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package me.eternal.purrfectsnap.ui.setup.screens.impl
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.delay
|
||||
import me.eternal.purrfectsnap.R
|
||||
import me.eternal.purrfectsnap.ui.manager.theme.PurrfectPalette
|
||||
import me.eternal.purrfectsnap.ui.setup.screens.SetupScreen
|
||||
|
||||
class IntroShowcaseScreen : SetupScreen() {
|
||||
private val slides = listOf(
|
||||
R.drawable.setup_slide_plus to "Unlock Snapchat Plus for free!",
|
||||
R.drawable.setup_slide_upload_tag to "Bypass the Media Upload tag!",
|
||||
R.drawable.setup_slide_downloads to "Download Snaps, & Spotlights!"
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
LaunchedEffect(Unit) { allowNext(true) }
|
||||
var currentIndex by remember { mutableIntStateOf(0) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
while (true) {
|
||||
delay(5000)
|
||||
currentIndex = (currentIndex + 1) % slides.size
|
||||
}
|
||||
}
|
||||
|
||||
SetupCard {
|
||||
StepTitle(
|
||||
title = "Welcome to PurrfectSnap",
|
||||
subtitle = "A quick look before setup begins",
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
AnimatedContent(targetState = currentIndex, label = "setupShowcase") { index ->
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(14.dp)
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(260.dp),
|
||||
shape = RoundedCornerShape(24.dp),
|
||||
color = PurrfectPalette.cardOverlayColor,
|
||||
border = BorderStroke(1.dp, Color.White.copy(alpha = 0.12f))
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(slides[index].first),
|
||||
contentDescription = slides[index].second,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(24.dp))
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = slides[index].second,
|
||||
color = Color.White,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
slides.forEachIndexed { index, _ ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(if (index == currentIndex) 10.dp else 8.dp)
|
||||
.clip(CircleShape)
|
||||
.background(if (index == currentIndex) PurrfectPalette.glowPrimary else Color.White.copy(alpha = 0.3f))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,8 @@ import kotlinx.coroutines.withContext
|
||||
import me.eternal.purrfectsnap.common.bridge.wrapper.LocaleWrapper
|
||||
import me.eternal.purrfectsnap.setup.patch.AutoPatchServer
|
||||
import me.eternal.purrfectsnap.setup.patch.LSPatch
|
||||
import me.eternal.purrfectsnap.ui.manager.ManagerAssistantDialog
|
||||
import me.eternal.purrfectsnap.ui.manager.Routes
|
||||
import me.eternal.purrfectsnap.ui.manager.components.AestheticDialog
|
||||
import me.eternal.purrfectsnap.ui.manager.theme.PurrfectPalette
|
||||
import me.eternal.purrfectsnap.ui.setup.screens.SetupScreen
|
||||
@@ -111,6 +113,10 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
var installWatcher by remember { mutableStateOf<Job?>(null) }
|
||||
var downloadFinished by rememberSaveable { mutableStateOf(false) }
|
||||
var showIssuesDialog by remember { mutableStateOf(false) }
|
||||
val assistantRoutes = remember {
|
||||
Routes(context).apply {
|
||||
}
|
||||
}
|
||||
val logPulse by rememberInfiniteTransition(label = "logPulse").animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 1f,
|
||||
@@ -313,62 +319,12 @@ class PatchSnapchatScreen : SetupScreen() {
|
||||
}
|
||||
|
||||
if (showIssuesDialog) {
|
||||
AestheticDialog(
|
||||
onDismissRequest = { showIssuesDialog = false },
|
||||
title = translation["setup.patch.issues_title"],
|
||||
text = "",
|
||||
icon = Icons.Filled.Info,
|
||||
confirmButtonText = translation["setup.patch.issues_confirm"],
|
||||
onConfirm = { showIssuesDialog = false },
|
||||
showCloseButton = false,
|
||||
customContent = {
|
||||
val bodyStyle = MaterialTheme.typography.bodyMedium.copy(
|
||||
color = PurrfectPalette.textSecondary,
|
||||
lineHeight = 18.sp
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 360.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
Text(
|
||||
text = translation["setup.patch.issues_heading"],
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Start,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Text(
|
||||
text = translation["setup.patch.issues_conflict_issue"],
|
||||
style = bodyStyle,
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
Text(
|
||||
text = translation["setup.patch.issues_conflict_fix"],
|
||||
style = bodyStyle,
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
Text(
|
||||
text = translation["setup.patch.issues_adb_command"],
|
||||
style = bodyStyle,
|
||||
textAlign = TextAlign.Start,
|
||||
softWrap = false,
|
||||
modifier = Modifier.horizontalScroll(rememberScrollState())
|
||||
)
|
||||
Text(
|
||||
text = translation["setup.patch.issues_invalid_issue"],
|
||||
style = bodyStyle,
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
Text(
|
||||
text = translation["setup.patch.issues_invalid_fix"],
|
||||
style = bodyStyle,
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
}
|
||||
}
|
||||
ManagerAssistantDialog(
|
||||
context = context,
|
||||
routes = assistantRoutes,
|
||||
initialUserMessage = "I am facing an App not installed issue or Package appears to be invalid issue while installing Snapchat. How do I fix it?",
|
||||
showImprovementLogging = false,
|
||||
onDismiss = { showIssuesDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
BIN
app/src/main/res/drawable/pfp_kaladin.jpg
Normal file
BIN
app/src/main/res/drawable/pfp_kaladin.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
BIN
app/src/main/res/drawable/pfp_schrodingerspet.jpg
Normal file
BIN
app/src/main/res/drawable/pfp_schrodingerspet.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
BIN
app/src/main/res/drawable/setup_slide_downloads.jpg
Normal file
BIN
app/src/main/res/drawable/setup_slide_downloads.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
app/src/main/res/drawable/setup_slide_plus.jpg
Normal file
BIN
app/src/main/res/drawable/setup_slide_plus.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
BIN
app/src/main/res/drawable/setup_slide_upload_tag.jpg
Normal file
BIN
app/src/main/res/drawable/setup_slide_upload_tag.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 354 KiB |
Reference in New Issue
Block a user