Skip to content

Commit c85767a

Browse files
committed
feat: Check update guide
Signed-off-by: Hu Shenghao <dede.hu@qq.com>
1 parent 41c7dd0 commit c85767a

File tree

7 files changed

+147
-79
lines changed

7 files changed

+147
-79
lines changed

app/src/foss/kotlin/com/dede/android_eggs/FlavorFeaturesImpl.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import android.app.Activity
44
import android.util.Log
55
import androidx.activity.ComponentActivity
66
import com.dede.android_eggs.flavor.FlavorFeatures
7-
import com.dede.android_eggs.flavor.LatestRelease
7+
import com.dede.android_eggs.flavor.LatestVersion
88
import io.ktor.client.HttpClient
99
import io.ktor.client.call.NoTransformationFoundException
1010
import io.ktor.client.call.body
@@ -26,7 +26,7 @@ class FlavorFeaturesImpl : FlavorFeatures {
2626
override fun launchReview(activity: ComponentActivity) {
2727
}
2828

29-
override suspend fun checkUpdate(activity: Activity): LatestRelease? {
29+
override suspend fun checkUpdate(activity: Activity): LatestVersion? {
3030
val client = HttpClient {
3131
install(Logging) {
3232
logger = object : Logger {
@@ -81,13 +81,13 @@ private data class GitHubLatestRelease(
8181
val contentType: String,
8282
)
8383

84-
fun toLatestRelease(): LatestRelease? {
84+
fun toLatestRelease(): LatestVersion? {
8585
val apkAsset =
8686
assets.firstOrNull { it.contentType == "application/vnd.android.package-archive" }
8787
if (apkAsset == null) {
8888
return null
8989
}
90-
return LatestRelease(
90+
return LatestVersion(
9191
versionName = tagName,
9292
pageUrl = htmlUrl,
9393
downloadUrl = apkAsset.downloadUrl,

app/src/main/java/com/dede/android_eggs/flavor/FlavorFeatures.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ interface FlavorFeatures {
1717

1818
fun launchReview(activity: ComponentActivity)
1919

20-
suspend fun checkUpdate(activity: Activity): LatestRelease?
20+
suspend fun checkUpdate(activity: Activity): LatestVersion?
2121
}

app/src/main/java/com/dede/android_eggs/flavor/LatestRelease.kt renamed to app/src/main/java/com/dede/android_eggs/flavor/LatestVersion.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.dede.android_eggs.flavor
22

3-
data class LatestRelease(
3+
data class LatestVersion(
44
val versionName: String,
55
val changelog: String,
66
val pageUrl: String,

app/src/main/java/com/dede/android_eggs/views/settings/compose/options/VersionOption.kt

Lines changed: 135 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,18 @@
33
package com.dede.android_eggs.views.settings.compose.options
44

55
import androidx.activity.compose.LocalActivity
6+
import androidx.compose.animation.core.LinearEasing
7+
import androidx.compose.animation.core.RepeatMode
8+
import androidx.compose.animation.core.animateFloat
9+
import androidx.compose.animation.core.infiniteRepeatable
10+
import androidx.compose.animation.core.rememberInfiniteTransition
11+
import androidx.compose.animation.core.tween
612
import androidx.compose.foundation.layout.Arrangement
13+
import androidx.compose.foundation.layout.Box
714
import androidx.compose.foundation.layout.Row
815
import androidx.compose.foundation.layout.Spacer
916
import androidx.compose.foundation.layout.fillMaxWidth
17+
import androidx.compose.foundation.layout.offset
1018
import androidx.compose.foundation.layout.width
1119
import androidx.compose.material.icons.Icons
1220
import androidx.compose.material.icons.automirrored.rounded.NavigateNext
@@ -29,21 +37,23 @@ import androidx.compose.runtime.rememberCoroutineScope
2937
import androidx.compose.runtime.setValue
3038
import androidx.compose.ui.Alignment
3139
import androidx.compose.ui.Modifier
40+
import androidx.compose.ui.graphics.graphicsLayer
3241
import androidx.compose.ui.platform.LocalContext
3342
import androidx.compose.ui.res.stringResource
43+
import androidx.compose.ui.text.LinkAnnotation
44+
import androidx.compose.ui.text.buildAnnotatedString
3445
import androidx.compose.ui.unit.dp
35-
import androidx.core.net.toUri
36-
import com.dede.android_eggs.BuildConfig
3746
import com.dede.android_eggs.R
3847
import com.dede.android_eggs.flavor.FlavorFeatures
39-
import com.dede.android_eggs.flavor.LatestRelease
48+
import com.dede.android_eggs.flavor.LatestVersion
4049
import com.dede.android_eggs.util.AGPUtils
4150
import com.dede.android_eggs.util.CustomTabsBrowser
4251
import com.dede.android_eggs.util.compareStringVersion
4352
import com.dede.android_eggs.views.main.compose.isAgreedPrivacyPolicy
4453
import com.dede.android_eggs.views.settings.compose.basic.Option
4554
import com.dede.android_eggs.views.settings.compose.basic.OptionShapes
4655
import com.dede.android_eggs.views.settings.compose.basic.imageVectorIconBlock
56+
import com.dede.basic.Utils
4757
import com.dede.basic.toast
4858
import kotlinx.coroutines.launch
4959
import com.dede.android_eggs.resources.R as StringR
@@ -52,45 +62,15 @@ import com.dede.android_eggs.resources.R as StringR
5262
@Composable
5363
fun VersionOption() {
5464
val context = LocalContext.current
55-
56-
var newRelease: LatestRelease? by remember { mutableStateOf(null) }
65+
val (versionName, versionCode) = remember(context) { Utils.getAppVersionPair(context) }
5766
Option(
5867
shape = OptionShapes.firstShape(),
5968
leadingIcon = imageVectorIconBlock(imageVector = Icons.Outlined.NewReleases),
60-
title = stringResource(
61-
R.string.label_version,
62-
BuildConfig.VERSION_NAME,
63-
BuildConfig.VERSION_CODE
64-
),
69+
title = stringResource(R.string.label_version, versionName, versionCode),
6570
desc = AGPUtils.getVcsRevision(7),
6671
trailingContent = {
6772
if (isAgreedPrivacyPolicy(context)) {
68-
val activity = LocalActivity.current
69-
val coroutineScope = rememberCoroutineScope()
70-
FilledTonalIconButton(
71-
shapes = IconButtonShapes(MaterialShapes.SoftBurst.toShape()),
72-
onClick = onClick@{
73-
if (activity == null) {
74-
return@onClick
75-
}
76-
coroutineScope.launch {
77-
val latestRelease = FlavorFeatures.get().checkUpdate(activity)
78-
if (latestRelease != null) {
79-
if (compareStringVersion(
80-
latestRelease.versionName,
81-
BuildConfig.VERSION_NAME
82-
) > 0
83-
) {
84-
newRelease = latestRelease
85-
} else {
86-
context.toast(StringR.string.toast_no_update_found)
87-
}
88-
}
89-
}
90-
}
91-
) {
92-
Icon(imageVector = Icons.Rounded.Upgrade, contentDescription = null)
93-
}
73+
UpgradeIconButton()
9474
} else {
9575
Icon(
9676
imageVector = Icons.AutoMirrored.Rounded.NavigateNext,
@@ -108,42 +88,129 @@ fun VersionOption() {
10888
CustomTabsBrowser.launchUrl(context, uri)
10989
}
11090
)
91+
}
92+
93+
@Composable
94+
private fun UpgradeIconButton() {
95+
var newVersion: LatestVersion? by remember { mutableStateOf(null) }
96+
Box(
97+
modifier = Modifier.offset(x = 4.dp),// fix padding end of Option
98+
contentAlignment = Alignment.Center
99+
) {
100+
val context = LocalContext.current
101+
val activity = LocalActivity.current
102+
val coroutineScope = rememberCoroutineScope()
111103

112-
if (newRelease != null) {
113-
val release = newRelease!!
114-
AlertDialog(
115-
onDismissRequest = { newRelease = null },
116-
title = {
117-
Row(
118-
modifier = Modifier.fillMaxWidth(),
119-
verticalAlignment = Alignment.CenterVertically,
120-
horizontalArrangement = Arrangement.Center,
121-
) {
122-
Icon(
123-
imageVector = Icons.Outlined.NewReleases,
124-
contentDescription = null,
125-
)
126-
Spacer(modifier = Modifier.width(10.dp))
127-
Text(release.versionName)
104+
var iconButtonAnimatable by remember { mutableStateOf(true) }
105+
val infiniteTransition =
106+
rememberInfiniteTransition(label = "UpgradeIconButtonInfiniteTransition")
107+
val scale by infiniteTransition.animateFloat(
108+
initialValue = 1f, targetValue = 1.2f,
109+
animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse)
110+
)
111+
val degrees by infiniteTransition.animateFloat(
112+
initialValue = 0f, targetValue = 360f,
113+
animationSpec = infiniteRepeatable(
114+
animation = tween(2200, easing = LinearEasing),
115+
repeatMode = RepeatMode.Restart
116+
)
117+
)
118+
FilledTonalIconButton(
119+
modifier = Modifier.graphicsLayer {
120+
if (iconButtonAnimatable) {
121+
scaleY = scale
122+
scaleX = scale
123+
rotationZ = degrees
128124
}
129125
},
130-
text = {
131-
Text(release.changelog)
132-
},
133-
confirmButton = {
134-
TextButton(
135-
onClick = {
136-
CustomTabsBrowser.launchUrl(context, release.downloadUrl)
137-
}
138-
) {
139-
Text(stringResource(StringR.string.label_update))
126+
shapes = IconButtonShapes(MaterialShapes.Cookie12Sided.toShape()),
127+
onClick = onClick@{
128+
if (activity == null) {
129+
iconButtonAnimatable = false
130+
return@onClick
140131
}
141-
},
142-
dismissButton = {
143-
TextButton(onClick = { newRelease = null }) {
144-
Text(stringResource(android.R.string.cancel))
132+
coroutineScope.launch {
133+
val latestVersion = FlavorFeatures.get().checkUpdate(activity)
134+
if (latestVersion != null) {
135+
if (compareStringVersion(
136+
latestVersion.versionName,
137+
Utils.getAppVersionPair(context).first
138+
) > 0
139+
) {
140+
newVersion = latestVersion
141+
} else {
142+
context.toast(StringR.string.toast_no_update_found)
143+
iconButtonAnimatable = false
144+
}
145+
} else {
146+
iconButtonAnimatable = false
147+
}
145148
}
146-
},
147-
)
149+
}
150+
) {
151+
// no content
152+
}
153+
Icon(imageVector = Icons.Rounded.Upgrade, contentDescription = null)
148154
}
155+
156+
if (newVersion != null) {
157+
UpgradeDialog(version = newVersion!!, onDismiss = { newVersion = null })
158+
}
159+
}
160+
161+
@Composable
162+
private fun UpgradeDialog(version: LatestVersion, onDismiss: () -> Unit) {
163+
val context = LocalContext.current
164+
AlertDialog(
165+
onDismissRequest = onDismiss,
166+
title = {
167+
Row(
168+
modifier = Modifier.fillMaxWidth(),
169+
verticalAlignment = Alignment.CenterVertically,
170+
horizontalArrangement = Arrangement.Center,
171+
) {
172+
Icon(
173+
imageVector = Icons.Outlined.NewReleases,
174+
contentDescription = null,
175+
)
176+
Spacer(modifier = Modifier.width(10.dp))
177+
Text(text = version.versionName)
178+
}
179+
},
180+
text = {
181+
val annotatedString = buildAnnotatedString {
182+
append(version.changelog)
183+
184+
val regex = Regex("#(\\d+)")
185+
regex.findAll(version.changelog)
186+
.forEach { match ->
187+
// issue or pull request id
188+
// github can automatically redirect to correct page
189+
val id = match.groupValues.getOrNull(1)
190+
val url = context.getString(R.string.url_github_issues_id, id)
191+
addLink(
192+
LinkAnnotation.Url(url = url, linkInteractionListener = {
193+
CustomTabsBrowser.launchUrl(context, url)
194+
}),
195+
match.range.first, match.range.last + 1
196+
)
197+
}
198+
}
199+
Text(text = annotatedString)
200+
},
201+
confirmButton = {
202+
TextButton(
203+
onClick = {
204+
CustomTabsBrowser.launchUrl(context, version.downloadUrl)
205+
}
206+
) {
207+
Text(text = stringResource(StringR.string.label_update))
208+
}
209+
},
210+
dismissButton = {
211+
TextButton(onClick = onDismiss) {
212+
Text(text = stringResource(android.R.string.cancel))
213+
}
214+
},
215+
)
149216
}

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<string name="url_github" translatable="false">https://github.com/hushenghao/AndroidEasterEggs</string>
55
<string name="url_github_commit" translatable="false">https://github.com/hushenghao/AndroidEasterEggs/commit/%s</string>
66
<string name="url_github_issues" translatable="false">https://github.com/hushenghao/AndroidEasterEggs/issues</string>
7+
<string name="url_github_issues_id" translatable="false">https://github.com/hushenghao/AndroidEasterEggs/issues/%s</string>
78
<string name="url_beta" translatable="false">https://github.com/hushenghao/AndroidEasterEggs/releases</string>
89
<string name="url_privacy" translatable="false">https://github.com/hushenghao/AndroidEasterEggs/wiki/Privacy-policy</string>
910
<string name="url_wiki" translatable="false">https://github.com/hushenghao/AndroidEasterEggs/wiki</string>

app/src/market/kotlin/com/dede/android_eggs/FlavorFeaturesImpl.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ package com.dede.android_eggs
33
import android.app.Activity
44
import androidx.activity.ComponentActivity
55
import com.dede.android_eggs.flavor.FlavorFeatures
6-
import com.dede.android_eggs.flavor.LatestRelease
6+
import com.dede.android_eggs.flavor.LatestVersion
77

88
class FlavorFeaturesImpl : FlavorFeatures {
99
override fun launchReview(activity: ComponentActivity) {
1010
GooglePlayCore.launchReview(activity)
1111
}
1212

13-
override suspend fun checkUpdate(activity: Activity): LatestRelease? {
13+
override suspend fun checkUpdate(activity: Activity): LatestVersion? {
1414
GooglePlayCore.checkUpdate(activity)
1515
return null
1616
}

basic/src/main/java/com/dede/basic/Utils.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ object Utils {
5353
}
5454
}
5555

56-
fun getAppVersionPair(context: Context): Pair<String?, Long> {
56+
fun getAppVersionPair(context: Context): Pair<String, Long> {
5757
var packageInfo: PackageInfo? = null
5858
try {
5959
packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
6060
} catch (ignore: NameNotFoundException) {
6161
}
6262
if (packageInfo != null) {
63-
val versionName = packageInfo.versionName
63+
val versionName = packageInfo.versionName ?: "-1"
6464
val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
6565
packageInfo.longVersionCode
6666
} else {
@@ -69,7 +69,7 @@ object Utils {
6969
}
7070
return versionName to versionCode
7171
}
72-
return null to -1L
72+
return "-1" to -1L
7373
}
7474

7575
}

0 commit comments

Comments
 (0)