Skip to content

Commit

Permalink
feat: Implement update detection for premium build
Browse files Browse the repository at this point in the history
Change-Id: I698ef9ec5d2a07ec23565a6f6392e31a65e1d106
  • Loading branch information
XayahSuSuSu committed Jul 10, 2024
1 parent 14bdcb0 commit 049be8a
Show file tree
Hide file tree
Showing 16 changed files with 178 additions and 27 deletions.
5 changes: 4 additions & 1 deletion source/app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
<string name="backup_dir_as_media_error">无法添加备份目录作为媒体资料</string>
<string name="media_target_path_missing">目标目录丢失,请手动设置</string>
<string name="media_set_path">设置路径</string>
<string name="update_available">有更新</string>
<string name="update_available">更新可用</string>
<string name="not_installed">未安装</string>
<string name="selected_both">两者均选</string>
<string name="abi_validation">验证ABI</string>
Expand Down Expand Up @@ -339,4 +339,7 @@
<string name="kill_app_options">杀死应用选项</string>
<string name="kill_app_options_desc">备份应用前行为</string>
<string name="language">语言</string>
<string name="download">下载</string>
<string name="changelog">更新日志</string>
<string name="args_update_from">你可以从%1$s版本更新到%2$s版本</string>
</resources>
3 changes: 3 additions & 0 deletions source/app/src/main/res/values-zh-rHK/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -339,4 +339,7 @@
<string name="kill_app_options">殺死應用選項</string>
<string name="kill_app_options_desc">備份應用前行爲</string>
<string name="language">語言</string>
<string name="download">下載</string>
<string name="changelog">更新日志</string>
<string name="args_update_from">你可以從%1$s版本更新到%2$s版本</string>
</resources>
5 changes: 4 additions & 1 deletion source/app/src/main/res/values-zh-rTW/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
<string name="backup_dir_as_media_error">無法添加備份目錄作為媒體資料</string>
<string name="media_target_path_missing">目標資料夾遺失。請手動設定。</string>
<string name="media_set_path">設定路徑</string>
<string name="update_available">有更新</string>
<string name="update_available">更新可用</string>
<string name="not_installed">未安裝</string>
<string name="selected_both">兩者均選</string>
<string name="abi_validation">驗證ABI</string>
Expand Down Expand Up @@ -339,4 +339,7 @@
<string name="kill_app_options">殺死應用選項</string>
<string name="kill_app_options_desc">備份應用前行爲</string>
<string name="language">語言</string>
<string name="download">下載</string>
<string name="changelog">更新日志</string>
<string name="args_update_from">你可以從%1$s版本更新到%2$s版本</string>
</resources>
3 changes: 3 additions & 0 deletions source/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -349,4 +349,7 @@
<string name="kill_app_options">Killing app options</string>
<string name="kill_app_options_desc">Behavior before backup</string>
<string name="language">Language</string>
<string name="download">Download</string>
<string name="changelog">Changelog</string>
<string name="args_update_from">You can update from version %1$s to version %2$s</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@ object ConstantUtil {
const val CHAT_LINK = "https://t.me/databackupchat"
const val DONATE_BMAC_LINK = "https://buymeacoffee.com/xayahsususu"
const val DONATE_AFD_LINK = "https://afdian.net/a/XayahSuSuSu"

const val FLAVOR_PREMIUM = "premium"
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@ private const val BASE_URL = "https://api.github.com/repos/XayahSuSuSu/Android-D
private interface Api {
@GET(value = "releases")
suspend fun getReleases(): List<Release>

@GET(value = "releases/latest")
suspend fun getLatestRelease(): Release
}

@Singleton
class GitHubNetwork @Inject constructor() {
class GitHubRepository @Inject constructor() {
private val service = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(Api::class.java)

suspend fun getReleases(): List<Release> = service.getReleases()
suspend fun getLatestRelease(): Release = service.getLatestRelease()
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
Expand Down Expand Up @@ -75,7 +76,7 @@ fun TextButton(modifier: Modifier = Modifier, text: StringResourceToken, onClick
TextButton(
modifier = modifier,
onClick = onClick,
content = { TitleSmallText(text = text.value, fontWeight = FontWeight.Bold) },
content = { Text(text = text.value, fontWeight = FontWeight.Bold) },
contentPadding = ButtonDefaults.ContentPadding
)
}
Expand Down
30 changes: 20 additions & 10 deletions source/core/ui/src/main/kotlin/com/xayah/core/ui/component/Chip.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.xayah.core.ui.component

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
Expand All @@ -19,22 +18,24 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Done
import androidx.compose.material.icons.rounded.KeyboardArrowDown
import androidx.compose.material.icons.rounded.KeyboardArrowUp
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.AssistChip
import androidx.compose.material3.AssistChipDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.graphics.Shape
import androidx.compose.ui.unit.Dp
import com.xayah.core.model.DataType
Expand Down Expand Up @@ -398,14 +399,23 @@ fun PackageDataChip(modifier: Modifier = Modifier, enabled: Boolean = true, data
)
}

@ExperimentalMaterial3Api
@Composable
fun RoundChip(modifier: Modifier = Modifier, label: @Composable () -> Unit) {
Box(
modifier = modifier
.clip(CircleShape)
.background(ColorSchemeKeyTokens.PrimaryContainer.toColor()),
contentAlignment = Alignment.Center
) {
label.invoke()
fun RoundChip(modifier: Modifier = Modifier, onClick: (() -> Unit)? = null, label: @Composable () -> Unit) {
CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) {
com.xayah.core.ui.material3.Surface(
modifier = modifier,
onClick = { onClick?.invoke() },
shape = CircleShape,
color = ColorSchemeKeyTokens.PrimaryContainer.toColor(),
indication = if (onClick != null) rememberRipple() else null,
) {
Box(
modifier = Modifier.wrapContentSize(),
contentAlignment = Alignment.Center
) {
label.invoke()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ fun PackageIcons(
}


@ExperimentalMaterial3Api
@ExperimentalLayoutApi
@ExperimentalFoundationApi
@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,95 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import com.xayah.core.common.util.BuildConfigUtil
import com.xayah.core.ui.R
import com.xayah.core.ui.material3.SnackbarHost
import com.xayah.core.ui.material3.SnackbarHostState
import com.xayah.core.ui.model.StringResourceToken
import com.xayah.core.ui.token.SizeTokens
import com.xayah.core.ui.util.fromStringId
import com.xayah.core.ui.util.getValue
import com.xayah.core.ui.util.value
import com.xayah.core.util.capitalizeString
import kotlinx.coroutines.delay

@ExperimentalAnimationApi
@ExperimentalMaterial3Api
@Composable
fun MainIndexSubScaffold(scrollBehavior: TopAppBarScrollBehavior, title: StringResourceToken, actions: @Composable RowScope.() -> Unit = {}, content: @Composable (BoxScope.() -> Unit)) {
fun MainIndexSubScaffold(
scrollBehavior: TopAppBarScrollBehavior,
snackbarHostState: SnackbarHostState? = null,
title: StringResourceToken,
updateAvailable: Boolean,
onVersionChipClick: (() -> Unit)? = null,
actions: @Composable RowScope.() -> Unit = {},
content: @Composable (BoxScope.() -> Unit)
) {
val context = LocalContext.current
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
TopAppBar(
title = {
Row(modifier = Modifier.height(IntrinsicSize.Min), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(SizeTokens.Level8)) {
Text(text = title.value)
RoundChip(modifier = Modifier.fillMaxHeight()) {
LabelLargeText(modifier = Modifier.paddingHorizontal(SizeTokens.Level8), text = "${BuildConfigUtil.VERSION_NAME} ${BuildConfigUtil.FLAVOR_feature.capitalizeString()}")

BadgedBox(
badge = {
if (updateAvailable)
Badge(modifier = Modifier.size(SizeTokens.Level6))
}
) {
RoundChip(modifier = Modifier.fillMaxHeight(), onClick = if (updateAvailable) onVersionChipClick else null) {
var version by remember {
mutableStateOf("${BuildConfigUtil.VERSION_NAME} ${BuildConfigUtil.FLAVOR_feature.capitalizeString()}")
}
LaunchedEffect(updateAvailable) {
while (updateAvailable) {
delay(3000)
val tmp = version
version = StringResourceToken.fromStringId(R.string.update_available).getValue(context)
delay(3000)
version = tmp
}
}
AnimatedTextContainer(targetState = version) { text ->
LabelLargeText(modifier = Modifier.paddingHorizontal(SizeTokens.Level12), text = text, maxLines = 1)
}
}
}
}
},
scrollBehavior = scrollBehavior,
actions = actions,
)
},
snackbarHost = {
if (snackbarHostState != null) {
SnackbarHost(
modifier = Modifier.paddingBottom(SizeTokens.Level24 + SizeTokens.Level4),
hostState = snackbarHostState,
)
}
},
) { innerPadding ->
Column {
InnerTopSpacer(innerPadding = innerPadding)
Expand Down
12 changes: 6 additions & 6 deletions source/core/ui/src/main/kotlin/com/xayah/core/ui/theme/Color.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ val Blue30 = Color(0xFF004B6F)
val Blue20 = Color(0xFF00344E)
val Blue10 = Color(0xFF001E2F)

val Green90 = Color(0xFF6BFCB6)
val Green80 = Color(0xFF4ADF9B)
val Green40 = Color(0xFF006C45)
val Green30 = Color(0xFF005233)
val Green20 = Color(0xFF003822)
val Green10 = Color(0xFF002112)
val Green90 = Color(0xFFC9ECCD)
val Green80 = Color(0xFFADD0B2)
val Green40 = Color(0xFF47664D)
val Green30 = Color(0xFF314E37)
val Green20 = Color(0xFF1A3722)
val Green10 = Color(0xFF05210E)

val Red90 = Color(0xFFFFD9E4)
val Red80 = Color(0xFFFFB0CD)
Expand Down
1 change: 1 addition & 0 deletions source/core/ui/src/main/res/values/ids.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<item name="specify_a_path" type="string" />
<item name="apk" type="string" />
<item name="data" type="string" />
<item name="update_available" type="string" />

<item name="jetbrains_mono_regular" type="font" />
<item name="ic_rounded_check_circle" type="drawable" />
Expand Down
1 change: 1 addition & 0 deletions source/feature/main/dashboard/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies {
implementation(project(":core:model"))
implementation(project(":core:data"))
implementation(project(":core:util"))
implementation(project(":core:network"))

// Compose Navigation
implementation(libs.androidx.navigation.compose)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.xayah.feature.main.dashboard

import android.annotation.SuppressLint
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
Expand All @@ -14,16 +15,21 @@ import androidx.compose.material.icons.outlined.Cloud
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.rounded.KeyboardArrowRight
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.xayah.core.common.util.BuildConfigUtil
import com.xayah.core.ui.component.IconButton
import com.xayah.core.ui.component.LocalSlotScope
import com.xayah.core.ui.component.MainIndexSubScaffold
import com.xayah.core.ui.component.Section
import com.xayah.core.ui.component.paddingTop
Expand All @@ -35,30 +41,58 @@ import com.xayah.core.ui.route.MainRoutes
import com.xayah.core.ui.token.SizeTokens
import com.xayah.core.ui.util.LocalNavController
import com.xayah.core.ui.util.fromDrawable
import com.xayah.core.ui.util.fromString
import com.xayah.core.ui.util.fromStringId
import com.xayah.core.ui.util.fromVector
import kotlinx.coroutines.launch

@SuppressLint("StringFormatInvalid")
@ExperimentalFoundationApi
@ExperimentalLayoutApi
@ExperimentalAnimationApi
@ExperimentalMaterial3Api
@Composable
fun PageDashboard() {
val context = LocalContext.current
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
val viewModel = hiltViewModel<IndexViewModel>()
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val navController = LocalNavController.current!!
val lastBackupTime by viewModel.lastBackupTimeState.collectAsStateWithLifecycle()
val directoryState by viewModel.directoryState.collectAsStateWithLifecycle()
val nullBackupDir by remember(directoryState) { mutableStateOf(directoryState == null) }
val dialogState = LocalSlotScope.current!!.dialogSlot
val scope = rememberCoroutineScope()

LaunchedEffect(null) {
viewModel.emitIntentOnIO(IndexUiIntent.Update)
}

MainIndexSubScaffold(
scrollBehavior = scrollBehavior,
snackbarHostState = viewModel.snackbarHostState,
title = StringResourceToken.fromStringId(R.string.app_name),
updateAvailable = uiState.latestRelease != null,
onVersionChipClick = {
scope.launch {
val state = dialogState.open(
initialState = false,
title = StringResourceToken.fromStringId(R.string.update_available),
icon = null,
dismissText = StringResourceToken.fromStringId(R.string.changelog),
confirmText = StringResourceToken.fromStringId(R.string.download),
block = { _ -> Text(text = context.getString(R.string.args_update_from, BuildConfigUtil.VERSION_NAME, uiState.latestRelease?.name)) }
).first
if (state) {
uiState.latestRelease?.assets?.firstOrNull { it.url.contains(BuildConfigUtil.FLAVOR_feature) && it.url.contains(BuildConfigUtil.FLAVOR_abi) }?.apply {
viewModel.emitIntent(IndexUiIntent.ToBrowser(context = context, url = this.url))
}
} else {
uiState.latestRelease?.url?.apply {
viewModel.emitIntent(IndexUiIntent.ToBrowser(context = context, url = this))
}
}
}
},
actions = {
IconButton(
enabled = nullBackupDir.not(),
Expand Down
Loading

0 comments on commit 049be8a

Please sign in to comment.