Merge pull request #1079 from Hy4ri/dev
release: merge dev into main for v1.26.4
This commit is contained in:
4 files changed
+97
-13
No files matched your search
@@ -600,6 +600,23 @@ fun ChatScreen(
|
|||||||
?: error("Application not available")
|
?: error("Application not available")
|
||||||
AppUpdateViewModel(app)
|
AppUpdateViewModel(app)
|
||||||
}
|
}
|
||||||
|
val appUpdateState by appUpdateViewModel.state.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val updateLifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
|
||||||
|
androidx.compose.runtime.DisposableEffect(updateLifecycleOwner) {
|
||||||
|
val observer =
|
||||||
|
androidx.lifecycle.LifecycleEventObserver { _, event ->
|
||||||
|
if (event == androidx.lifecycle.Lifecycle.Event.ON_RESUME) {
|
||||||
|
if (appUpdateState is AppUpdateState.NeedsUnknownSourcesPermission) {
|
||||||
|
appUpdateViewModel.resumeInstallAfterPermission()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateLifecycleOwner.lifecycle.addObserver(observer)
|
||||||
|
onDispose {
|
||||||
|
updateLifecycleOwner.lifecycle.removeObserver(observer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (UpdateNoticeManager.enabled && !AppUpdateCache.dismissed) {
|
if (UpdateNoticeManager.enabled && !AppUpdateCache.dismissed) {
|
||||||
val noticeTag =
|
val noticeTag =
|
||||||
@@ -616,8 +633,14 @@ fun ChatScreen(
|
|||||||
|
|
||||||
if (AppUpdateCache.isDialogVisible) {
|
if (AppUpdateCache.isDialogVisible) {
|
||||||
val context = androidx.compose.ui.platform.LocalContext.current
|
val context = androidx.compose.ui.platform.LocalContext.current
|
||||||
|
val dialogState =
|
||||||
|
if (appUpdateState !is AppUpdateState.Idle) {
|
||||||
|
appUpdateState
|
||||||
|
} else {
|
||||||
|
updateNotice
|
||||||
|
}
|
||||||
AppUpdateDialog(
|
AppUpdateDialog(
|
||||||
state = updateNotice,
|
state = dialogState,
|
||||||
onDismiss = { AppUpdateCache.hideDialog() },
|
onDismiss = { AppUpdateCache.hideDialog() },
|
||||||
onStartUpdate = { appUpdateViewModel.startUpdate() },
|
onStartUpdate = { appUpdateViewModel.startUpdate() },
|
||||||
onCancelDownload = { appUpdateViewModel.cancelDownload() },
|
onCancelDownload = { appUpdateViewModel.cancelDownload() },
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ import androidx.compose.material3.Surface
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
@@ -49,14 +53,19 @@ fun AppUpdateDialog(
|
|||||||
onNeverAskAgain: () -> Unit,
|
onNeverAskAgain: () -> Unit,
|
||||||
onOpenSettings: () -> Unit,
|
onOpenSettings: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val tag = state.releaseTag().orEmpty()
|
|
||||||
val available = state as? AppUpdateState.UpdateAvailable
|
val available = state as? AppUpdateState.UpdateAvailable
|
||||||
|
var cachedAvailable by remember { mutableStateOf(available) }
|
||||||
|
if (available != null) {
|
||||||
|
cachedAvailable = available
|
||||||
|
}
|
||||||
|
val currentAvailable = available ?: cachedAvailable
|
||||||
|
val tag = state.releaseTag() ?: currentAvailable?.latestTag.orEmpty()
|
||||||
val sizeMb =
|
val sizeMb =
|
||||||
available?.let {
|
currentAvailable?.let {
|
||||||
val mb = it.sizeBytes / (1024 * 1024.0)
|
val mb = it.sizeBytes / (1024 * 1024.0)
|
||||||
"%.1f MB".format(mb)
|
"%.1f MB".format(mb)
|
||||||
}
|
}
|
||||||
val releaseNotes = available?.releaseNotes?.trim().orEmpty()
|
val releaseNotes = currentAvailable?.releaseNotes?.trim().orEmpty()
|
||||||
|
|
||||||
Dialog(onDismissRequest = onDismiss) {
|
Dialog(onDismissRequest = onDismiss) {
|
||||||
Surface(
|
Surface(
|
||||||
@@ -156,6 +165,7 @@ fun AppUpdateDialog(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
val progressPercent = (state.progress * 100).toInt()
|
val progressPercent = (state.progress * 100).toInt()
|
||||||
Text(
|
Text(
|
||||||
@@ -167,8 +177,18 @@ fun AppUpdateDialog(
|
|||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
|
if (currentAvailable != null && currentAvailable.sizeBytes > 0) {
|
||||||
|
val downloadedMb = (state.progress * currentAvailable.sizeBytes) / (1024 * 1024.0)
|
||||||
|
val totalMb = currentAvailable.sizeBytes / (1024 * 1024.0)
|
||||||
|
Text(
|
||||||
|
text = "%.1f / %.1f MB".format(downloadedMb, totalMb),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
}
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
if (state.progress > 0f) {
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
progress = { state.progress },
|
progress = { state.progress },
|
||||||
modifier =
|
modifier =
|
||||||
@@ -177,6 +197,15 @@ fun AppUpdateDialog(
|
|||||||
.height(8.dp)
|
.height(8.dp)
|
||||||
.clip(RoundedCornerShape(4.dp)),
|
.clip(RoundedCornerShape(4.dp)),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(8.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp)),
|
||||||
|
)
|
||||||
|
}
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = onCancelDownload,
|
onClick = onCancelDownload,
|
||||||
@@ -188,11 +217,21 @@ fun AppUpdateDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is AppUpdateState.Installing -> {
|
is AppUpdateState.Installing -> {
|
||||||
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.settings_about_update_installing, tag),
|
text = stringResource(R.string.settings_about_update_installing, tag),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(8.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp)),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is AppUpdateState.NeedsUnknownSourcesPermission -> {
|
is AppUpdateState.NeedsUnknownSourcesPermission -> {
|
||||||
|
|||||||
@@ -43,10 +43,14 @@ class AppUpdateViewModel(
|
|||||||
val state: StateFlow<AppUpdateState> = _state.asStateFlow()
|
val state: StateFlow<AppUpdateState> = _state.asStateFlow()
|
||||||
|
|
||||||
private var downloadJob: kotlinx.coroutines.Job? = null
|
private var downloadJob: kotlinx.coroutines.Job? = null
|
||||||
|
private var lastAvailable: AppUpdateState.UpdateAvailable? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val cached = AppUpdateCache.state.value
|
val cached = AppUpdateCache.state.value
|
||||||
if (cached is AppUpdateState.UpdateAvailable || cached is AppUpdateState.UpToDate) {
|
if (cached is AppUpdateState.UpdateAvailable) {
|
||||||
|
lastAvailable = cached
|
||||||
|
_state.value = cached
|
||||||
|
} else if (cached is AppUpdateState.UpToDate) {
|
||||||
_state.value = cached
|
_state.value = cached
|
||||||
} else {
|
} else {
|
||||||
checkForUpdate()
|
checkForUpdate()
|
||||||
@@ -83,12 +87,13 @@ class AppUpdateViewModel(
|
|||||||
}
|
}
|
||||||
_state.value =
|
_state.value =
|
||||||
if (isNewerVersion(info.tagName, currentVersion)) {
|
if (isNewerVersion(info.tagName, currentVersion)) {
|
||||||
AppUpdateState.UpdateAvailable(
|
AppUpdateState
|
||||||
|
.UpdateAvailable(
|
||||||
latestTag = info.tagName,
|
latestTag = info.tagName,
|
||||||
apkUrl = apk.browserDownloadUrl,
|
apkUrl = apk.browserDownloadUrl,
|
||||||
sizeBytes = apk.size,
|
sizeBytes = apk.size,
|
||||||
releaseNotes = info.body,
|
releaseNotes = info.body,
|
||||||
)
|
).also { lastAvailable = it }
|
||||||
} else {
|
} else {
|
||||||
AppUpdateState.UpToDate(latestTag = info.tagName)
|
AppUpdateState.UpToDate(latestTag = info.tagName)
|
||||||
}
|
}
|
||||||
@@ -100,7 +105,12 @@ class AppUpdateViewModel(
|
|||||||
|
|
||||||
/** Download the release APK and launch the system installer. */
|
/** Download the release APK and launch the system installer. */
|
||||||
fun startUpdate() {
|
fun startUpdate() {
|
||||||
val available = _state.value as? AppUpdateState.UpdateAvailable ?: return
|
val available =
|
||||||
|
(_state.value as? AppUpdateState.UpdateAvailable)
|
||||||
|
?: lastAvailable
|
||||||
|
?: (AppUpdateCache.state.value as? AppUpdateState.UpdateAvailable)
|
||||||
|
?: return
|
||||||
|
lastAvailable = available
|
||||||
if (!canRequestInstalls()) {
|
if (!canRequestInstalls()) {
|
||||||
_state.value = AppUpdateState.NeedsUnknownSourcesPermission
|
_state.value = AppUpdateState.NeedsUnknownSourcesPermission
|
||||||
return
|
return
|
||||||
@@ -129,7 +139,9 @@ class AppUpdateViewModel(
|
|||||||
fun cancelDownload() {
|
fun cancelDownload() {
|
||||||
downloadJob?.cancel()
|
downloadJob?.cancel()
|
||||||
downloadJob = null
|
downloadJob = null
|
||||||
val available = AppUpdateCache.state.value as? AppUpdateState.UpdateAvailable
|
val available =
|
||||||
|
lastAvailable
|
||||||
|
?: (AppUpdateCache.state.value as? AppUpdateState.UpdateAvailable)
|
||||||
if (available != null) {
|
if (available != null) {
|
||||||
_state.value = available
|
_state.value = available
|
||||||
} else {
|
} else {
|
||||||
@@ -139,7 +151,10 @@ class AppUpdateViewModel(
|
|||||||
|
|
||||||
/** Dismiss the current update tag so it stops nagging. */
|
/** Dismiss the current update tag so it stops nagging. */
|
||||||
fun dismissCurrentUpdate() {
|
fun dismissCurrentUpdate() {
|
||||||
val tag = _state.value.releaseTag()
|
val tag =
|
||||||
|
_state.value.releaseTag()
|
||||||
|
?: lastAvailable?.latestTag
|
||||||
|
?: AppUpdateCache.state.value.releaseTag()
|
||||||
if (tag != null) {
|
if (tag != null) {
|
||||||
AuthManager.setDismissedUpdateTag(tag)
|
AuthManager.setDismissedUpdateTag(tag)
|
||||||
}
|
}
|
||||||
@@ -152,8 +167,9 @@ class AppUpdateViewModel(
|
|||||||
if (canRequestInstalls()) {
|
if (canRequestInstalls()) {
|
||||||
val dest = File(getApplication<Application>().cacheDir, APK_FILE_NAME)
|
val dest = File(getApplication<Application>().cacheDir, APK_FILE_NAME)
|
||||||
val available =
|
val available =
|
||||||
_state.value as? AppUpdateState.UpdateAvailable
|
(_state.value as? AppUpdateState.UpdateAvailable)
|
||||||
?: AppUpdateCache.state.value as? AppUpdateState.UpdateAvailable
|
?: lastAvailable
|
||||||
|
?: (AppUpdateCache.state.value as? AppUpdateState.UpdateAvailable)
|
||||||
if (dest.exists() && dest.length() > 0 && available != null) {
|
if (dest.exists() && dest.length() > 0 && available != null) {
|
||||||
_state.value = AppUpdateState.Installing(available.latestTag)
|
_state.value = AppUpdateState.Installing(available.latestTag)
|
||||||
launchInstaller(dest)
|
launchInstaller(dest)
|
||||||
|
|||||||
@@ -178,10 +178,16 @@ private fun UpdateRow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is AppUpdateState.Downloading -> {
|
is AppUpdateState.Downloading -> {
|
||||||
|
if (state.progress > 0f) {
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
progress = { state.progress },
|
progress = { state.progress },
|
||||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is AppUpdateState.NeedsUnknownSourcesPermission -> {
|
is AppUpdateState.NeedsUnknownSourcesPermission -> {
|
||||||
|
|||||||
Reference in new issue
Block a user