cosmo.17: tap-the-rocket hint, green/red/amber status dot (stronger green pill), card header = small buttons 'Обновить подписку'/'Проверить серверы', traffic 'used / ∞' + expiry line (no info/telegram icons), bottom bar Home + My subscription back
This commit is contained in:
parent
4e57cc1145
commit
03c7f7119d
4 changed files with 119 additions and 56 deletions
|
|
@ -16,7 +16,7 @@ android {
|
|||
minSdk = 24
|
||||
targetSdk = 37
|
||||
versionCode = 749
|
||||
versionName = "2.3.9-cosmo.16"
|
||||
versionName = "2.3.9-cosmo.17"
|
||||
|
||||
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
||||
splits {
|
||||
|
|
|
|||
|
|
@ -144,8 +144,21 @@ fun CosmoMainScreen(
|
|||
else onAction(MainAction.UpdateSubscriptions)
|
||||
}
|
||||
|
||||
// No bottom bar: settings (gear, top-left) and "My subscription" (ⓘ in the card) open as separate screens.
|
||||
Scaffold { padding ->
|
||||
// Bottom bar: Home + My subscription; Settings opens from the gear (top-left) as a separate screen.
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
NavigationBar {
|
||||
CosmoTab.entries.forEachIndexed { index, item ->
|
||||
NavigationBarItem(
|
||||
selected = tab == index,
|
||||
onClick = { tab = index },
|
||||
icon = { Icon(painterResource(item.icon), contentDescription = null) },
|
||||
label = { Text(stringResource(item.label)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
Box(Modifier.padding(padding).fillMaxSize()) {
|
||||
when (tab) {
|
||||
0 -> CosmoHomeTab(
|
||||
|
|
@ -156,14 +169,12 @@ fun CosmoMainScreen(
|
|||
onOpenSubscription = { tab = 1 },
|
||||
onOpenSettings = { tab = 2 },
|
||||
)
|
||||
1 -> CosmoSubPage(onBack = { tab = 0 }) {
|
||||
CosmoSubscriptionTab(
|
||||
ui = ui,
|
||||
serverCount = group.rows.size,
|
||||
onAction = onAction,
|
||||
onRenew = { showRenew = true },
|
||||
)
|
||||
}
|
||||
1 -> CosmoSubscriptionTab(
|
||||
ui = ui,
|
||||
serverCount = group.rows.size,
|
||||
onAction = onAction,
|
||||
onRenew = { showRenew = true },
|
||||
)
|
||||
else -> CosmoSubPage(onBack = { tab = 0 }) {
|
||||
CosmoSettingsTab(
|
||||
onAction = onAction,
|
||||
|
|
@ -176,7 +187,12 @@ fun CosmoMainScreen(
|
|||
}
|
||||
}
|
||||
|
||||
/** Secondary screen with a back arrow (Home has no bottom bar). */
|
||||
private enum class CosmoTab(@DrawableRes val icon: Int, @StringRes val label: Int) {
|
||||
Home(R.drawable.ic_cosmo_home_24dp, R.string.cosmo_tab_home),
|
||||
Subscription(R.drawable.ic_subscriptions_24dp, R.string.cosmo_tab_subscription),
|
||||
}
|
||||
|
||||
/** Secondary screen with a back arrow (Settings, opened from the gear). */
|
||||
@Composable
|
||||
private fun CosmoSubPage(onBack: () -> Unit, content: @Composable () -> Unit) {
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
|
|
@ -335,26 +351,44 @@ private fun CosmoHomeTab(
|
|||
color = if (state == CosmoConnState.Connected) MaterialTheme.colorScheme.onSurface
|
||||
else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
|
||||
)
|
||||
// Make it obvious that the rocket circle is the on/off button.
|
||||
Text(
|
||||
stringResource(
|
||||
when (state) {
|
||||
CosmoConnState.Connected -> R.string.cosmo_tap_to_disconnect
|
||||
CosmoConnState.Connecting -> R.string.cosmo_hint_connecting
|
||||
CosmoConnState.Disconnected -> R.string.cosmo_tap_to_connect
|
||||
}
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- One card: header (name + update/test icons), info line, fastest, servers ----
|
||||
item {
|
||||
Column(Modifier.fillMaxWidth().clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)).background(cardBg)) {
|
||||
Row(Modifier.fillMaxWidth().padding(start = 16.dp, end = 4.dp, top = 4.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(start = 12.dp, end = 4.dp, top = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
CosmoSmallButton(
|
||||
icon = R.drawable.ic_cosmo_refresh_24dp,
|
||||
text = stringResource(R.string.cosmo_update_sub),
|
||||
onClick = { onAction(MainAction.UpdateSubscriptions) },
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
CosmoSmallButton(
|
||||
icon = R.drawable.ic_cosmo_speed_24dp,
|
||||
text = stringResource(R.string.cosmo_check_servers),
|
||||
busy = ui.isTesting && !findingFastest,
|
||||
enabled = !ui.isTesting,
|
||||
onClick = { onAction(MainAction.TestRealAllServers) },
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
IconButton(onClick = { onAction(MainAction.UpdateSubscriptions) }) {
|
||||
Icon(painterResource(R.drawable.ic_cosmo_refresh_24dp), contentDescription = stringResource(R.string.cosmo_update_sub), tint = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
IconButton(onClick = { onAction(MainAction.TestRealAllServers) }, enabled = !ui.isTesting) {
|
||||
if (ui.isTesting && !findingFastest) CircularProgressIndicator(Modifier.size(20.dp), strokeWidth = 2.dp)
|
||||
else Icon(painterResource(R.drawable.ic_cosmo_speed_24dp), contentDescription = stringResource(R.string.cosmo_check_servers), tint = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
IconButton(onClick = { listExpanded = !listExpanded }) {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_expand_more_24dp),
|
||||
|
|
@ -363,7 +397,7 @@ private fun CosmoHomeTab(
|
|||
)
|
||||
}
|
||||
}
|
||||
CosmoSubscriptionInfoRow(subInfo, onOpenSubscription)
|
||||
CosmoSubscriptionInfoRow(subInfo)
|
||||
}
|
||||
}
|
||||
if (listExpanded) {
|
||||
|
|
@ -422,18 +456,46 @@ private fun CosmoHomeTab(
|
|||
}
|
||||
}
|
||||
|
||||
/** Compact line under the card header: traffic | expiry | details (ⓘ) | support. */
|
||||
/** Small outlined button with an icon and a short label (card header). */
|
||||
@Composable
|
||||
private fun CosmoSubscriptionInfoRow(info: CosmoSubscriptionInfo?, onOpenSubscription: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
Row(Modifier.fillMaxWidth().padding(start = 16.dp, end = 12.dp, bottom = 10.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
private fun CosmoSmallButton(
|
||||
@DrawableRes icon: Int,
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
busy: Boolean = false,
|
||||
) {
|
||||
OutlinedButton(
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 8.dp),
|
||||
modifier = modifier.height(40.dp),
|
||||
) {
|
||||
if (busy) CircularProgressIndicator(Modifier.size(16.dp), strokeWidth = 2.dp)
|
||||
else Icon(painterResource(icon), contentDescription = null, modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text(text, style = MaterialTheme.typography.labelLarge, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
}
|
||||
|
||||
/** Compact line under the card buttons: used traffic / limit (∞) on the left, expiry date on the right. */
|
||||
@Composable
|
||||
private fun CosmoSubscriptionInfoRow(info: CosmoSubscriptionInfo?) {
|
||||
Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
val traffic = when {
|
||||
info == null -> "—"
|
||||
info.isUnlimitedTraffic -> stringResource(R.string.cosmo_sub_unlimited)
|
||||
else -> stringResource(R.string.cosmo_sub_traffic_left, info.trafficLeftBytes.toTrafficString())
|
||||
info.isUnlimitedTraffic -> "${(info.upload + info.download).toTrafficString()} / ∞"
|
||||
else -> "${(info.upload + info.download).toTrafficString()} / ${info.total.toTrafficString()}"
|
||||
}
|
||||
Text(traffic, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1)
|
||||
Spacer(Modifier.width(10.dp))
|
||||
Box(
|
||||
Modifier.border(BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant), RoundedCornerShape(8.dp))
|
||||
.padding(horizontal = 10.dp, vertical = 3.dp),
|
||||
) {
|
||||
Text(traffic, style = MaterialTheme.typography.bodyMedium, maxLines = 1)
|
||||
}
|
||||
Spacer(Modifier.weight(1f))
|
||||
val expire = info?.expire?.takeIf { it > 0 }?.times(1000)
|
||||
val expired = info?.isExpired() == true
|
||||
if (expire != null && expire - System.currentTimeMillis() < NO_EXPIRY_THRESHOLD_DAYS * 86_400_000L) {
|
||||
|
|
@ -442,23 +504,8 @@ private fun CosmoSubscriptionInfoRow(info: CosmoSubscriptionInfo?, onOpenSubscri
|
|||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = if (expired) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
} else {
|
||||
Spacer(Modifier.weight(1f))
|
||||
}
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_cosmo_info_24dp),
|
||||
contentDescription = stringResource(R.string.cosmo_tab_subscription),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(36.dp).clip(CircleShape).clickable(onClick = onOpenSubscription).padding(6.dp),
|
||||
)
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_telegram_24dp),
|
||||
contentDescription = stringResource(R.string.cosmo_mysub_support),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(36.dp).clip(CircleShape).clickable { Utils.openUri(context, "https://t.me/cosmonetvpn_bot") }.padding(6.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -544,15 +591,27 @@ private fun CosmoStatusPill(state: CosmoConnState, large: Boolean = false) {
|
|||
CosmoConnState.Connecting -> Triple(
|
||||
MaterialTheme.colorScheme.secondaryContainer, MaterialTheme.colorScheme.onSecondaryContainer, R.string.cosmo_state_connecting
|
||||
)
|
||||
CosmoConnState.Connected -> Triple(Color(0xFFC8F5DC), Color(0xFF0F4A2C), R.string.cosmo_state_connected)
|
||||
CosmoConnState.Connected -> Triple(Color(0xFF1FA35C), Color.White, R.string.cosmo_state_connected)
|
||||
}
|
||||
// Status dot: green = connected, red = not connected, amber = connecting.
|
||||
val dot = when (state) {
|
||||
CosmoConnState.Connected -> Color(0xFF7CFFB0)
|
||||
CosmoConnState.Connecting -> Color(0xFFFFC14D)
|
||||
CosmoConnState.Disconnected -> Color(0xFFFF5252)
|
||||
}
|
||||
Surface(color = bg, contentColor = fg, shape = RoundedCornerShape(50)) {
|
||||
Text(
|
||||
stringResource(label),
|
||||
style = if (large) MaterialTheme.typography.titleSmall else MaterialTheme.typography.labelMedium,
|
||||
modifier = if (large) Modifier.padding(horizontal = 16.dp, vertical = 7.dp)
|
||||
else Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
|
||||
)
|
||||
Row(
|
||||
Modifier.padding(horizontal = if (large) 16.dp else 10.dp, vertical = if (large) 7.dp else 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(Modifier.size(if (large) 10.dp else 8.dp).clip(CircleShape).background(dot))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
stringResource(label),
|
||||
style = if (large) MaterialTheme.typography.titleSmall else MaterialTheme.typography.labelMedium,
|
||||
fontWeight = if (state == CosmoConnState.Connected) FontWeight.SemiBold else FontWeight.Normal,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@
|
|||
<string name="cosmo_tab_home">Главная</string>
|
||||
<string name="cosmo_tab_countries">Страны</string>
|
||||
<string name="cosmo_tab_subscription">Моя подписка</string>
|
||||
<string name="cosmo_tap_to_connect">Нажмите на ракету, чтобы подключиться</string>
|
||||
<string name="cosmo_tap_to_disconnect">Нажмите на ракету, чтобы отключиться</string>
|
||||
<string name="cosmo_hide_all">Свернуть список</string>
|
||||
<string name="cosmo_show_all">Показать серверы</string>
|
||||
<string name="cosmo_expires">Истекает: %1$s</string>
|
||||
<string name="cosmo_update_sub">Обновить подписку</string>
|
||||
<string name="cosmo_check_servers">Проверить доступность</string>
|
||||
<string name="cosmo_check_servers">Проверить серверы</string>
|
||||
<string name="cosmo_pick_server">Выберите сервер из списка ниже</string>
|
||||
<string name="cosmo_mysub_active">Подписка активна</string>
|
||||
<string name="cosmo_mysub_expired">Подписка закончилась</string>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
<string name="cosmo_tab_home">Home</string>
|
||||
<string name="cosmo_tab_countries">Countries</string>
|
||||
<string name="cosmo_tab_subscription">My subscription</string>
|
||||
<string name="cosmo_tap_to_connect">Tap the rocket to connect</string>
|
||||
<string name="cosmo_tap_to_disconnect">Tap the rocket to disconnect</string>
|
||||
<string name="cosmo_hide_all">Collapse list</string>
|
||||
<string name="cosmo_show_all">Show servers</string>
|
||||
<string name="cosmo_expires">Expires: %1$s</string>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue