Add update check for Gitee source

This commit is contained in:
Ash 2022-04-24 07:17:41 +08:00
parent 0cb60d15b2
commit 52d6b0698d
14 changed files with 231 additions and 98 deletions

View File

@ -13,11 +13,13 @@ class CrashHandler(private val context: Context) : UncaughtExceptionHandler {
}
override fun uncaughtException(p0: Thread, p1: Throwable) {
if (Looper.myLooper() == null) {
Looper.prepare()
}
Toast.makeText(context, p1.message, Toast.LENGTH_LONG).show()
Looper.loop()
p1.printStackTrace()
Log.e("RLog", "uncaughtException: ${p1.message}" )
Log.e("RLog", "uncaughtException: ${p1.message}")
android.os.Process.killProcess(android.os.Process.myPid());
exitProcess(1)
}

View File

@ -1,6 +1,6 @@
package me.ash.reader.data.entity
data class GitHubRelease(
data class LatestRelease(
val html_url: String? = null,
val tag_name: String? = null,
val name: String? = null,

View File

@ -0,0 +1,30 @@
package me.ash.reader.data.entity
class Version(identifiers: List<String>) {
private var major: Int = 0
private var minor: Int = 0
private var point: Int = 0
init {
major = identifiers.getOrNull(0)?.toIntOrNull() ?: 0
minor = identifiers.getOrNull(1)?.toIntOrNull() ?: 0
point = identifiers.getOrNull(2)?.toIntOrNull() ?: 0
}
constructor() : this(listOf())
constructor(string: String?) : this(string?.split(".") ?: listOf())
fun whetherNeedUpdate(current: Version, skip: Version): Boolean = this > current && this > skip
operator fun compareTo(target: Version): Int = when {
major > target.major -> 1
major < target.major -> -1
minor > target.minor -> 1
minor < target.minor -> -1
point > target.point -> 1
point < target.point -> -1
else -> 0
}
override fun toString() = "$major.$minor.$point"
}

View File

@ -6,13 +6,12 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext
import me.ash.reader.R
import me.ash.reader.data.entity.Version
import me.ash.reader.data.module.ApplicationScope
import me.ash.reader.data.module.DispatcherIO
import me.ash.reader.data.source.AppNetworkDataSource
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put
import me.ash.reader.ui.ext.skipVersionNumber
import me.ash.reader.ui.ext.*
import javax.inject.Inject
class AppRepository @Inject constructor(
@ -24,13 +23,16 @@ class AppRepository @Inject constructor(
@DispatcherIO
private val dispatcherIO: CoroutineDispatcher,
) {
suspend fun checkUpdate() {
withContext(dispatcherIO) {
try {
val latest = appNetworkDataSource.getReleaseLatest()
val latestVersion = latest.tag_name?.formatVersion() ?: listOf()
suspend fun checkUpdate(): Boolean = withContext(dispatcherIO) {
return@withContext try {
val latest =
appNetworkDataSource.getReleaseLatest(context.getString(R.string.update_link))
val latestVersion = Version(latest.tag_name)
// val latestVersion = Version("0.7.3")
val skipVersion = Version(context.skipVersionNumber)
val currentVersion = context.getCurrentVersion()
val latestLog = latest.body ?: ""
val latestPublishDate = latest.published_at ?: ""
val latestPublishDate = latest.published_at ?: latest.created_at ?: ""
val latestSize = latest.assets
?.first()
?.size
@ -39,40 +41,26 @@ class AppRepository @Inject constructor(
?.first()
?.browser_download_url
?: ""
val currentVersion = context
.packageManager
.getPackageInfo(context.packageName, 0)
.versionName
.formatVersion()
Log.i("RLog", "current version ${currentVersion.joinToString(".")}")
if (latestVersion > context.skipVersionNumber.formatVersion() && latestVersion > currentVersion) {
Log.i("RLog", "new version ${latestVersion.joinToString(".")}")
Log.i("RLog", "current version $currentVersion")
if (latestVersion.whetherNeedUpdate(currentVersion, skipVersion)) {
Log.i("RLog", "new version $latestVersion")
context.dataStore.put(
DataStoreKeys.NewVersionNumber,
latestVersion.joinToString(".")
latestVersion.toString()
)
context.dataStore.put(DataStoreKeys.NewVersionLog, latestLog)
context.dataStore.put(DataStoreKeys.NewVersionPublishDate, latestPublishDate)
context.dataStore.put(DataStoreKeys.NewVersionSize, latestSize)
context.dataStore.put(DataStoreKeys.NewVersionDownloadUrl, latestDownloadUrl)
true
} else {
false
}
this
} catch (e: Exception) {
e.printStackTrace()
Log.e("RLog", "checkUpdate: ${e.message}")
}
false
}
}
}
fun String.formatVersion(): List<String> = this.split(".")
operator fun List<String>.compareTo(target: List<String>): Int {
for (i in 0 until minOf(size, target.size)) {
val a = this[i].toIntOrNull() ?: 0
val b = target[i].toIntOrNull() ?: 0
if (a < b) return -1
if (a > b) return 1
}
return if (size == target.size) 0 else if (size > target.size) 1 else -1
}

View File

@ -1,13 +1,14 @@
package me.ash.reader.data.source
import me.ash.reader.data.entity.GitHubRelease
import me.ash.reader.data.entity.LatestRelease
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Url
interface AppNetworkDataSource {
@GET("https://api.github.com/repos/Ashinch/ReadYou/releases/latest")
suspend fun getReleaseLatest(): GitHubRelease
@GET
suspend fun getReleaseLatest(@Url url: String): LatestRelease
companion object {
private var instance: AppNetworkDataSource? = null

View File

@ -8,6 +8,7 @@
package me.ash.reader.ui.component
import android.view.SoundEffectConstants
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@ -21,6 +22,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@ -36,6 +38,8 @@ fun Banner(
action: (@Composable () -> Unit)? = null,
onClick: () -> Unit = {},
) {
val view = LocalView.current
Surface(
modifier = modifier
.fillMaxWidth()
@ -48,7 +52,10 @@ fun Banner(
.padding(horizontal = 16.dp)
.clip(RoundedCornerShape(32.dp))
.background(backgroundColor alwaysLight true)
.clickable { onClick() }
.clickable {
view.playSoundEffect(SoundEffectConstants.CLICK)
onClick()
}
.padding(16.dp, 20.dp),
verticalAlignment = Alignment.CenterVertically
) {

View File

@ -3,9 +3,13 @@ package me.ash.reader.ui.ext
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import me.ash.reader.data.entity.Version
fun Context.findActivity(): Activity? = when (this) {
is Activity -> this
is ContextWrapper -> baseContext.findActivity()
else -> null
}
fun Context.getCurrentVersion(): Version =
Version(packageManager.getPackageInfo(packageName, 0).versionName)

View File

@ -29,9 +29,8 @@ import androidx.navigation.NavHostController
import androidx.work.WorkInfo
import kotlinx.coroutines.flow.map
import me.ash.reader.R
import me.ash.reader.data.entity.Version
import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing
import me.ash.reader.data.repository.compareTo
import me.ash.reader.data.repository.formatVersion
import me.ash.reader.ui.component.Banner
import me.ash.reader.ui.component.DisplayText
import me.ash.reader.ui.component.FeedbackIconButton
@ -62,21 +61,20 @@ fun FeedsPage(
onScrollToPage: (targetPage: Int) -> Unit = {},
) {
val context = LocalContext.current
val skipVersionNumber =
context.dataStore.data
.map { it[DataStoreKeys.SkipVersionNumber.key] ?: "" }
.collectAsState(initial = "")
.value
.formatVersion()
val newVersionNumber =
context.dataStore.data
.map { it[DataStoreKeys.NewVersionNumber.key] ?: "" }
.collectAsState(initial = "")
.value
.formatVersion()
val viewState = feedsViewModel.viewState.collectAsStateValue()
val skipVersion = context.dataStore.data
.map { it[DataStoreKeys.SkipVersionNumber.key] ?: "" }
.map { Version(it) }
.collectAsState(initial = Version())
.value
val latestVersion = context.dataStore.data
.map { it[DataStoreKeys.NewVersionNumber.key] ?: "" }
.map { Version(it) }
.collectAsState(initial = Version())
.value
val currentVersion by remember { mutableStateOf(context.getCurrentVersion()) }
val owner = LocalLifecycleOwner.current
var isSyncing by remember { mutableStateOf(false) }
syncWorkLiveData.observe(owner) {
@ -132,7 +130,7 @@ fun FeedsPage(
imageVector = Icons.Outlined.Settings,
contentDescription = stringResource(R.string.settings),
tint = MaterialTheme.colorScheme.onSurface,
showBadge = newVersionNumber > skipVersionNumber,
showBadge = latestVersion.whetherNeedUpdate(currentVersion, skipVersion),
) {
navController.navigate(RouteName.SETTINGS)
}

View File

@ -18,13 +18,13 @@ import androidx.compose.ui.zIndex
import androidx.navigation.NavHostController
import kotlinx.coroutines.flow.map
import me.ash.reader.R
import me.ash.reader.data.repository.compareTo
import me.ash.reader.data.repository.formatVersion
import me.ash.reader.data.entity.Version
import me.ash.reader.ui.component.Banner
import me.ash.reader.ui.component.DisplayText
import me.ash.reader.ui.component.FeedbackIconButton
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.getCurrentVersion
import me.ash.reader.ui.page.common.RouteName
import me.ash.reader.ui.theme.palette.onLight
@ -36,18 +36,17 @@ fun SettingsPage(
) {
val context = LocalContext.current
var updateDialogVisible by remember { mutableStateOf(false) }
val skipVersionNumber =
context.dataStore.data
val skipVersion = context.dataStore.data
.map { it[DataStoreKeys.SkipVersionNumber.key] ?: "" }
.collectAsState(initial = "")
.map { Version(it) }
.collectAsState(initial = Version())
.value
.formatVersion()
val newVersionNumber =
context.dataStore.data
val latestVersion = context.dataStore.data
.map { it[DataStoreKeys.NewVersionNumber.key] ?: "" }
.collectAsState(initial = "")
.map { Version(it) }
.collectAsState(initial = Version())
.value
.formatVersion()
val currentVersion by remember { mutableStateOf(context.getCurrentVersion()) }
Scaffold(
modifier = Modifier
@ -78,13 +77,13 @@ fun SettingsPage(
}
item {
Box {
if (newVersionNumber > skipVersionNumber) {
if (latestVersion.whetherNeedUpdate(currentVersion, skipVersion)) {
Banner(
modifier = Modifier.zIndex(1f),
title = stringResource(R.string.get_new_updates),
desc = stringResource(
R.string.get_new_updates_desc,
newVersionNumber.joinToString(".")
latestVersion.toString(),
),
icon = Icons.Outlined.Lightbulb,
action = {

View File

@ -4,6 +4,7 @@ import android.content.Intent
import android.net.Uri
import android.view.HapticFeedbackConstants
import android.view.SoundEffectConstants
import android.widget.Toast
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
@ -31,10 +32,13 @@ import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import kotlinx.coroutines.launch
import me.ash.reader.R
import me.ash.reader.ui.component.CurlyCornerShape
import me.ash.reader.ui.component.FeedbackIconButton
import me.ash.reader.ui.ext.*
import me.ash.reader.ui.theme.palette.alwaysLight
import me.ash.reader.ui.theme.palette.onLight
@ -42,12 +46,16 @@ import me.ash.reader.ui.theme.palette.onLight
@Composable
fun TipsAndSupport(
navController: NavHostController,
updateViewModel: UpdateViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val view = LocalView.current
val scope = rememberCoroutineScope()
val viewState = updateViewModel.viewState.collectAsStateValue()
val githubLink = stringResource(R.string.github_link)
val telegramLink = stringResource(R.string.telegram_link)
var version by remember { mutableStateOf("") }
val isLatestVersion = stringResource(R.string.is_latest_version)
var currentVersion by remember { mutableStateOf("") }
var pressAMP by remember { mutableStateOf(16f) }
val animatedPress by animateFloatAsState(
targetValue = pressAMP,
@ -55,7 +63,7 @@ fun TipsAndSupport(
)
LaunchedEffect(Unit) {
version = context.packageManager.getPackageInfo(context.packageName, 0).versionName
currentVersion = context.getCurrentVersion().toString()
}
Scaffold(
@ -100,6 +108,28 @@ fun TipsAndSupport(
view.playSoundEffect(SoundEffectConstants.CLICK)
pressAMP = 16f
},
onTap = {
scope.launch {
context.dataStore.put(DataStoreKeys.SkipVersionNumber, "")
updateViewModel.dispatch(
UpdateViewAction.CheckUpdate(
{
context.dataStore.put(
DataStoreKeys.SkipVersionNumber,
""
)
},
{
if (!it) Toast.makeText(
context,
isLatestVersion,
Toast.LENGTH_SHORT
).show()
}
)
)
}
}
)
},
horizontalAlignment = Alignment.CenterHorizontally,
@ -135,7 +165,7 @@ fun TipsAndSupport(
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
contentColor = MaterialTheme.colorScheme.tertiary,
) {
Text(text = version)
Text(text = currentVersion)
}
}
) {
@ -199,6 +229,11 @@ fun TipsAndSupport(
}
}
)
UpdateDialog(
visible = viewState.updateDialogVisible,
onDismissRequest = { updateViewModel.dispatch(UpdateViewAction.Hide) },
)
}
@Immutable

View File

@ -58,6 +58,7 @@ fun UpdateDialog(
val newVersionSize = context.dataStore.data
.map { it[DataStoreKeys.NewVersionSize.key] ?: 0 }
.map { it / 1024f / 1024f }
.map { if (it > 0f) " ${String.format("%.2f", it)} MB" else "" }
.collectAsState(initial = 0)
.value
@ -98,7 +99,7 @@ fun UpdateDialog(
onClick = {
}
) {
Text(text = stringResource(R.string.update, String.format("%.2f", newVersionSize)))
Text(text = stringResource(R.string.update) + newVersionSize)
}
},
dismissButton = {

View File

@ -0,0 +1,66 @@
package me.ash.reader.ui.page.settings
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import me.ash.reader.data.repository.AppRepository
import javax.inject.Inject
@HiltViewModel
class UpdateViewModel @Inject constructor(
private val appRepository: AppRepository,
) : ViewModel() {
private val _viewState = MutableStateFlow(UpdateViewState())
val viewState: StateFlow<UpdateViewState> = _viewState.asStateFlow()
fun dispatch(action: UpdateViewAction) {
when (action) {
is UpdateViewAction.Show -> changeUpdateDialogVisible(true)
is UpdateViewAction.Hide -> changeUpdateDialogVisible(false)
is UpdateViewAction.CheckUpdate -> checkUpdate(
action.preProcessor,
action.postProcessor
)
}
}
private fun checkUpdate(
preProcessor: suspend () -> Unit = {},
postProcessor: suspend (Boolean) -> Unit = {}
) {
viewModelScope.launch {
preProcessor()
appRepository.checkUpdate().let {
if (it) changeUpdateDialogVisible(true)
postProcessor(it)
}
}
}
private fun changeUpdateDialogVisible(visible: Boolean) {
_viewState.update {
it.copy(
updateDialogVisible = visible
)
}
}
}
data class UpdateViewState(
val updateDialogVisible: Boolean = false,
)
sealed class UpdateViewAction {
object Show : UpdateViewAction()
object Hide : UpdateViewAction()
data class CheckUpdate(
val preProcessor: suspend () -> Unit = {},
val postProcessor: suspend (Boolean) -> Unit = {}
) : UpdateViewAction()
}

View File

@ -111,8 +111,9 @@
<string name="open_source_licenses">开放源代码许可</string>
<string name="github_link">https://github.com/Ashinch/ReadYou</string>
<string name="telegram_link">https://t.me/ReadYouApp</string>
<string name="update_link">https://api.github.com/repos/Ashinch/ReadYou/releases/latest</string>
<string name="update_link">https://gitee.com/api/v5/repos/Ashinch/ReadYou/releases/latest</string>
<string name="change_log">更新日志</string>
<string name="update">更新 %1$s MB</string>
<string name="update">更新</string>
<string name="skip_this_version">跳过这个版本</string>
<string name="is_latest_version">已是最新版本</string>
</resources>

View File

@ -113,6 +113,7 @@
<string name="telegram_link">https://t.me/ReadYouApp</string>
<string name="update_link">https://api.github.com/repos/Ashinch/ReadYou/releases/latest</string>
<string name="change_log">Change Log</string>
<string name="update">Update %1$s MB</string>
<string name="update">Update</string>
<string name="skip_this_version">Skip This Version</string>
<string name="is_latest_version">This is the latest version</string>
</resources>