33package com.dede.android_eggs.views.settings.compose.options
44
55import 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
612import androidx.compose.foundation.layout.Arrangement
13+ import androidx.compose.foundation.layout.Box
714import androidx.compose.foundation.layout.Row
815import androidx.compose.foundation.layout.Spacer
916import androidx.compose.foundation.layout.fillMaxWidth
17+ import androidx.compose.foundation.layout.offset
1018import androidx.compose.foundation.layout.width
1119import androidx.compose.material.icons.Icons
1220import androidx.compose.material.icons.automirrored.rounded.NavigateNext
@@ -29,21 +37,23 @@ import androidx.compose.runtime.rememberCoroutineScope
2937import androidx.compose.runtime.setValue
3038import androidx.compose.ui.Alignment
3139import androidx.compose.ui.Modifier
40+ import androidx.compose.ui.graphics.graphicsLayer
3241import androidx.compose.ui.platform.LocalContext
3342import androidx.compose.ui.res.stringResource
43+ import androidx.compose.ui.text.LinkAnnotation
44+ import androidx.compose.ui.text.buildAnnotatedString
3445import androidx.compose.ui.unit.dp
35- import androidx.core.net.toUri
36- import com.dede.android_eggs.BuildConfig
3746import com.dede.android_eggs.R
3847import com.dede.android_eggs.flavor.FlavorFeatures
39- import com.dede.android_eggs.flavor.LatestRelease
48+ import com.dede.android_eggs.flavor.LatestVersion
4049import com.dede.android_eggs.util.AGPUtils
4150import com.dede.android_eggs.util.CustomTabsBrowser
4251import com.dede.android_eggs.util.compareStringVersion
4352import com.dede.android_eggs.views.main.compose.isAgreedPrivacyPolicy
4453import com.dede.android_eggs.views.settings.compose.basic.Option
4554import com.dede.android_eggs.views.settings.compose.basic.OptionShapes
4655import com.dede.android_eggs.views.settings.compose.basic.imageVectorIconBlock
56+ import com.dede.basic.Utils
4757import com.dede.basic.toast
4858import kotlinx.coroutines.launch
4959import com.dede.android_eggs.resources.R as StringR
@@ -52,45 +62,15 @@ import com.dede.android_eggs.resources.R as StringR
5262@Composable
5363fun 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}
0 commit comments