diff --git a/app/src/main/java/me/ash/reader/App.kt b/app/src/main/java/me/ash/reader/App.kt index 9cd6687..64f1e0c 100644 --- a/app/src/main/java/me/ash/reader/App.kt +++ b/app/src/main/java/me/ash/reader/App.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.launch import me.ash.reader.data.module.ApplicationScope import me.ash.reader.data.module.DispatcherDefault import me.ash.reader.data.repository.* +import me.ash.reader.data.source.AppNetworkDataSource import me.ash.reader.data.source.OpmlLocalDataSource import me.ash.reader.data.source.ReaderDatabase import me.ash.reader.data.source.RssNetworkDataSource @@ -30,6 +31,9 @@ class App : Application(), Configuration.Provider { @Inject lateinit var workManager: WorkManager + @Inject + lateinit var appNetworkDataSource: AppNetworkDataSource + @Inject lateinit var opmlLocalDataSource: OpmlLocalDataSource @@ -39,6 +43,9 @@ class App : Application(), Configuration.Provider { @Inject lateinit var rssHelper: RssHelper + @Inject + lateinit var appRepository: AppRepository + @Inject lateinit var stringsRepository: StringsRepository @@ -72,6 +79,7 @@ class App : Application(), Configuration.Provider { applicationScope.launch(dispatcherDefault) { accountInit() workerInit() + checkUpdate() } } @@ -90,6 +98,10 @@ class App : Application(), Configuration.Provider { rssRepository.get().doSync() } + private suspend fun checkUpdate() { + appRepository.checkUpdate() + } + override fun getWorkManagerConfiguration(): Configuration = Configuration.Builder() .setWorkerFactory(workerFactory) diff --git a/app/src/main/java/me/ash/reader/CrashHandler.kt b/app/src/main/java/me/ash/reader/CrashHandler.kt index 3c99cdf..12cbfbb 100644 --- a/app/src/main/java/me/ash/reader/CrashHandler.kt +++ b/app/src/main/java/me/ash/reader/CrashHandler.kt @@ -1,6 +1,7 @@ package me.ash.reader import android.content.Context +import android.os.Looper import android.util.Log import android.widget.Toast import java.lang.Thread.UncaughtExceptionHandler @@ -12,7 +13,9 @@ class CrashHandler(private val context: Context) : UncaughtExceptionHandler { } override fun uncaughtException(p0: Thread, p1: Throwable) { + Looper.prepare() Toast.makeText(context, p1.message, Toast.LENGTH_LONG).show() + Looper.loop() p1.printStackTrace() Log.e("RLog", "uncaughtException: ${p1.message}" ) android.os.Process.killProcess(android.os.Process.myPid()); diff --git a/app/src/main/java/me/ash/reader/data/entity/GitHubRelease.kt b/app/src/main/java/me/ash/reader/data/entity/GitHubRelease.kt new file mode 100644 index 0000000..ab65a43 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/entity/GitHubRelease.kt @@ -0,0 +1,23 @@ +package me.ash.reader.data.entity + +data class GitHubRelease( + val html_url: String? = null, + val tag_name: String? = null, + val name: String? = null, + val draft: Boolean? = null, + val prerelease: Boolean? = null, + val created_at: String? = null, + val published_at: String? = null, + val assets: List? = null, + val body: String? = null, +) + +data class AssetsItem( + val name: String? = null, + val content_type: String? = null, + val size: Int? = null, + val download_count: Int? = null, + val created_at: String? = null, + val updated_at: String? = null, + val browser_download_url: String? = null, +) \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/module/RetrofitModule.kt b/app/src/main/java/me/ash/reader/data/module/RetrofitModule.kt index b9b97d6..7ae99d9 100644 --- a/app/src/main/java/me/ash/reader/data/module/RetrofitModule.kt +++ b/app/src/main/java/me/ash/reader/data/module/RetrofitModule.kt @@ -4,6 +4,7 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import me.ash.reader.data.source.AppNetworkDataSource import me.ash.reader.data.source.FeverApiDataSource import me.ash.reader.data.source.GoogleReaderApiDataSource import me.ash.reader.data.source.RssNetworkDataSource @@ -13,6 +14,11 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) object RetrofitModule { + @Provides + @Singleton + fun provideAppNetworkDataSource(): AppNetworkDataSource = + AppNetworkDataSource.getInstance() + @Provides @Singleton fun provideRssNetworkDataSource(): RssNetworkDataSource = diff --git a/app/src/main/java/me/ash/reader/data/repository/AppRepository.kt b/app/src/main/java/me/ash/reader/data/repository/AppRepository.kt new file mode 100644 index 0000000..c692c3c --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/repository/AppRepository.kt @@ -0,0 +1,78 @@ +package me.ash.reader.data.repository + +import android.content.Context +import android.util.Log +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.withContext +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 javax.inject.Inject + +class AppRepository @Inject constructor( + @ApplicationContext + private val context: Context, + private val appNetworkDataSource: AppNetworkDataSource, + @ApplicationScope + private val applicationScope: CoroutineScope, + @DispatcherIO + private val dispatcherIO: CoroutineDispatcher, +) { + suspend fun checkUpdate() { + withContext(dispatcherIO) { + try { + val latest = appNetworkDataSource.getReleaseLatest() + val latestVersion = latest.tag_name?.formatVersion() ?: listOf() + val latestLog = latest.body ?: "" + val latestPublishDate = latest.published_at ?: "" + val latestSize = latest.assets + ?.first() + ?.size + ?: 0 + val latestDownloadUrl = latest.assets + ?.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(".")}") + context.dataStore.put( + DataStoreKeys.NewVersionNumber, + latestVersion.joinToString(".") + ) + context.dataStore.put(DataStoreKeys.NewVersionLog, latestLog) + context.dataStore.put(DataStoreKeys.NewVersionPublishDate, latestPublishDate) + context.dataStore.put(DataStoreKeys.NewVersionSize, latestSize) + context.dataStore.put(DataStoreKeys.NewVersionDownloadUrl, latestDownloadUrl) + } + this + } catch (e: Exception) { + Log.e("RLog", "checkUpdate: ${e.message}") + } + } + } +} + +fun String.formatVersion(): List = this.split(".") + +operator fun List.compareTo(target: List): 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 +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/source/AppNetworkDataSource.kt b/app/src/main/java/me/ash/reader/data/source/AppNetworkDataSource.kt new file mode 100644 index 0000000..9e7e1d6 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/source/AppNetworkDataSource.kt @@ -0,0 +1,26 @@ +package me.ash.reader.data.source + +import me.ash.reader.data.entity.GitHubRelease +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.http.GET + +interface AppNetworkDataSource { + @GET("https://api.github.com/repos/Ashinch/ReadYou/releases/latest") + suspend fun getReleaseLatest(): GitHubRelease + + companion object { + private var instance: AppNetworkDataSource? = null + + fun getInstance(): AppNetworkDataSource { + return instance ?: synchronized(this) { + instance ?: Retrofit.Builder() + .baseUrl("https://api.github.com/") + .addConverterFactory(GsonConverterFactory.create()) + .build().create(AppNetworkDataSource::class.java).also { + instance = it + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/component/Banner.kt b/app/src/main/java/me/ash/reader/ui/component/Banner.kt index bce64ec..ef12aff 100644 --- a/app/src/main/java/me/ash/reader/ui/component/Banner.kt +++ b/app/src/main/java/me/ash/reader/ui/component/Banner.kt @@ -31,6 +31,7 @@ fun Banner( modifier: Modifier = Modifier, title: String, desc: String? = null, + backgroundColor: Color = MaterialTheme.colorScheme.primaryContainer, icon: ImageVector? = null, action: (@Composable () -> Unit)? = null, onClick: () -> Unit = {}, @@ -46,7 +47,7 @@ fun Banner( .fillMaxSize() .padding(horizontal = 16.dp) .clip(RoundedCornerShape(32.dp)) - .background(MaterialTheme.colorScheme.primaryContainer alwaysLight true) + .background(backgroundColor alwaysLight true) .clickable { onClick() } .padding(16.dp, 20.dp), verticalAlignment = Alignment.CenterVertically diff --git a/app/src/main/java/me/ash/reader/ui/component/FeedbackIconButton.kt b/app/src/main/java/me/ash/reader/ui/component/FeedbackIconButton.kt index 0e00ea6..9c2433d 100644 --- a/app/src/main/java/me/ash/reader/ui/component/FeedbackIconButton.kt +++ b/app/src/main/java/me/ash/reader/ui/component/FeedbackIconButton.kt @@ -2,14 +2,16 @@ package me.ash.reader.ui.component import android.view.HapticFeedbackConstants import android.view.SoundEffectConstants -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LocalContentColor +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.* import androidx.compose.runtime.Composable 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.unit.dp @Composable fun FeedbackIconButton( @@ -17,6 +19,7 @@ fun FeedbackIconButton( imageVector: ImageVector, contentDescription: String?, tint: Color = LocalContentColor.current, + showBadge: Boolean = false, isHaptic: Boolean? = true, isSound: Boolean? = true, onClick: () -> Unit = {}, @@ -30,11 +33,32 @@ fun FeedbackIconButton( onClick() }, ) { - Icon( - modifier = modifier, - imageVector = imageVector, - contentDescription = contentDescription, - tint = tint, - ) + if (showBadge) { + BadgedBox( + badge = { + Badge( + modifier = Modifier + .size(8.dp) + .clip(CircleShape), + containerColor = MaterialTheme.colorScheme.error, + contentColor = MaterialTheme.colorScheme.onError, + ) + } + ) { + Icon( + modifier = modifier, + imageVector = imageVector, + contentDescription = contentDescription, + tint = tint, + ) + } + } else { + Icon( + modifier = modifier, + imageVector = imageVector, + contentDescription = contentDescription, + tint = tint, + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/ext/DataStoreExt.kt b/app/src/main/java/me/ash/reader/ui/ext/DataStoreExt.kt index f7d0d98..3f9faae 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/DataStoreExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/DataStoreExt.kt @@ -12,6 +12,19 @@ import kotlinx.coroutines.runBlocking import java.io.IOException val Context.dataStore: DataStore by preferencesDataStore(name = "settings") + +val Context.newVersionPublishDate: String + get() = this.dataStore.get(DataStoreKeys.NewVersionPublishDate) ?: "" +val Context.newVersionLog: String + get() = this.dataStore.get(DataStoreKeys.NewVersionLog) ?: "" +val Context.newVersionSize: Int + get() = this.dataStore.get(DataStoreKeys.NewVersionSize) ?: 0 +val Context.newVersionDownloadUrl: String + get() = this.dataStore.get(DataStoreKeys.NewVersionDownloadUrl) ?: "" +val Context.newVersionNumber: String + get() = this.dataStore.get(DataStoreKeys.NewVersionNumber) ?: "" +val Context.skipVersionNumber: String + get() = this.dataStore.get(DataStoreKeys.SkipVersionNumber) ?: "" val Context.isFirstLaunch: Boolean get() = this.dataStore.get(DataStoreKeys.IsFirstLaunch) ?: true val Context.currentAccountId: Int @@ -62,6 +75,36 @@ sealed class DataStoreKeys { get() = booleanPreferencesKey("isFirstLaunch") } + object NewVersionPublishDate : DataStoreKeys() { + override val key: Preferences.Key + get() = stringPreferencesKey("newVersionPublishDate") + } + + object NewVersionLog : DataStoreKeys() { + override val key: Preferences.Key + get() = stringPreferencesKey("newVersionLog") + } + + object NewVersionSize : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("newVersionSize") + } + + object NewVersionDownloadUrl : DataStoreKeys() { + override val key: Preferences.Key + get() = stringPreferencesKey("newVersionDownloadUrl") + } + + object NewVersionNumber : DataStoreKeys() { + override val key: Preferences.Key + get() = stringPreferencesKey("newVersionNumber") + } + + object SkipVersionNumber : DataStoreKeys() { + override val key: Preferences.Key + get() = stringPreferencesKey("skipVersionNumber") + } + object CurrentAccountId : DataStoreKeys() { override val key: Preferences.Key get() = intPreferencesKey("currentAccountId") diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt index 3148f1a..6bf0243 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt @@ -1,5 +1,6 @@ package me.ash.reader.ui.page.home.feeds +import android.annotation.SuppressLint import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.core.* @@ -26,15 +27,16 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.LiveData import androidx.navigation.NavHostController import androidx.work.WorkInfo +import kotlinx.coroutines.flow.map import me.ash.reader.R 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 import me.ash.reader.ui.component.Subtitle -import me.ash.reader.ui.ext.collectAsStateValue -import me.ash.reader.ui.ext.getDesc -import me.ash.reader.ui.ext.getName +import me.ash.reader.ui.ext.* import me.ash.reader.ui.page.common.RouteName import me.ash.reader.ui.page.home.FilterBar import me.ash.reader.ui.page.home.FilterState @@ -42,6 +44,7 @@ import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeDialog import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewAction import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewModel +@SuppressLint("FlowOperatorInvokedInComposition") @OptIn( ExperimentalMaterial3Api::class, com.google.accompanist.pager.ExperimentalPagerApi::class, androidx.compose.foundation.ExperimentalFoundationApi::class @@ -59,6 +62,19 @@ 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 owner = LocalLifecycleOwner.current @@ -116,6 +132,7 @@ fun FeedsPage( imageVector = Icons.Outlined.Settings, contentDescription = stringResource(R.string.settings), tint = MaterialTheme.colorScheme.onSurface, + showBadge = newVersionNumber > skipVersionNumber, ) { navController.navigate(RouteName.SETTINGS) } diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/SelectableSettingGroupItem.kt b/app/src/main/java/me/ash/reader/ui/page/settings/SelectableSettingGroupItem.kt index b78ba98..51bf01b 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/SelectableSettingGroupItem.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/SelectableSettingGroupItem.kt @@ -8,6 +8,7 @@ package me.ash.reader.ui.page.settings +import android.view.SoundEffectConstants import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -25,6 +26,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -38,8 +40,15 @@ fun SelectableSettingGroupItem( icon: ImageVector? = null, onClick: () -> Unit, ) { + val view = LocalView.current + Surface( - modifier = modifier.clickable { onClick() }.alpha(if (enable) 1f else 0.5f), + modifier = modifier + .clickable { + view.playSoundEffect(SoundEffectConstants.CLICK) + onClick() + } + .alpha(if (enable) 1f else 0.5f), color = Color.Unspecified, ) { Row( diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt index d330d25..85754f2 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt @@ -1,33 +1,54 @@ package me.ash.reader.ui.page.settings +import android.annotation.SuppressLint import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.* import androidx.compose.material.icons.rounded.ArrowBack import androidx.compose.material.icons.rounded.Close import androidx.compose.material3.* -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +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.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.page.common.RouteName import me.ash.reader.ui.theme.palette.onLight +@SuppressLint("FlowOperatorInvokedInComposition") @OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsPage( navController: NavHostController, ) { + val context = LocalContext.current + var updateDialogVisible by remember { mutableStateOf(false) } + 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() + Scaffold( modifier = Modifier .background(MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface) @@ -56,17 +77,30 @@ fun SettingsPage( DisplayText(text = stringResource(R.string.settings), desc = "") } item { - Banner( - title = stringResource(R.string.in_coding), - desc = stringResource(R.string.coming_soon), - icon = Icons.Outlined.Lightbulb, - action = { - Icon( - imageVector = Icons.Rounded.Close, - contentDescription = stringResource(R.string.close), - ) - }, - ) + Box { + if (newVersionNumber > skipVersionNumber) { + Banner( + modifier = Modifier.zIndex(1f), + title = stringResource(R.string.get_new_updates), + desc = stringResource( + R.string.get_new_updates_desc, + newVersionNumber.joinToString(".") + ), + icon = Icons.Outlined.Lightbulb, + action = { + Icon( + imageVector = Icons.Rounded.Close, + contentDescription = stringResource(R.string.close), + ) + }, + ) { updateDialogVisible = true } + } + Banner( + title = stringResource(R.string.in_coding), + desc = stringResource(R.string.coming_soon), + icon = Icons.Outlined.Lightbulb, + ) + } Spacer(modifier = Modifier.height(16.dp)) } item { @@ -114,4 +148,9 @@ fun SettingsPage( } } ) + + UpdateDialog( + visible = updateDialogVisible, + onDismissRequest = { updateDialogVisible = false }, + ) } diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/UpdateDialog.kt b/app/src/main/java/me/ash/reader/ui/page/settings/UpdateDialog.kt new file mode 100644 index 0000000..d737c38 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/settings/UpdateDialog.kt @@ -0,0 +1,115 @@ +package me.ash.reader.ui.page.settings + +import android.annotation.SuppressLint +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Update +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.google.accompanist.pager.ExperimentalPagerApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import me.ash.reader.R +import me.ash.reader.ui.component.Dialog +import me.ash.reader.ui.ext.DataStoreKeys +import me.ash.reader.ui.ext.dataStore +import me.ash.reader.ui.ext.put + +@SuppressLint("FlowOperatorInvokedInComposition") +@OptIn(ExperimentalPagerApi::class) +@Composable +fun UpdateDialog( + modifier: Modifier = Modifier, + visible: Boolean = false, + onDismissRequest: () -> Unit = {}, + onConfirm: (String) -> Unit = {}, +) { + val context = LocalContext.current + val scope = rememberCoroutineScope { Dispatchers.IO } + val newVersionNumber = context.dataStore.data + .map { it[DataStoreKeys.NewVersionNumber.key] ?: "" } + .collectAsState(initial = "") + .value + val newVersionPublishDate = context.dataStore.data + .map { it[DataStoreKeys.NewVersionPublishDate.key] ?: "" } + .collectAsState(initial = "") + .value + val newVersionLog = context.dataStore.data + .map { it[DataStoreKeys.NewVersionLog.key] ?: "" } + .collectAsState(initial = "") + .value + val newVersionSize = context.dataStore.data + .map { it[DataStoreKeys.NewVersionSize.key] ?: 0 } + .map { it / 1024f / 1024f } + .collectAsState(initial = 0) + .value + + Dialog( + modifier = modifier.heightIn(max = 400.dp), + visible = visible, + onDismissRequest = onDismissRequest, + icon = { + Icon( + imageVector = Icons.Rounded.Update, + contentDescription = stringResource(R.string.change_log), + ) + }, + title = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text(text = stringResource(R.string.change_log)) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = newVersionPublishDate, + color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f), + style = MaterialTheme.typography.bodyMedium, + ) + Spacer(modifier = Modifier.height(16.dp)) + } + }, + text = { + SelectionContainer { + Text( + modifier = Modifier.verticalScroll(rememberScrollState()), + text = newVersionLog, + ) + } + }, + confirmButton = { + TextButton( + onClick = { + } + ) { + Text(text = stringResource(R.string.update, String.format("%.2f", newVersionSize))) + } + }, + dismissButton = { + TextButton(onClick = { + scope.launch { + context.dataStore.put(DataStoreKeys.SkipVersionNumber, newVersionNumber) + onDismissRequest() + } + }) { + Text(text = stringResource(R.string.skip_this_version)) + } + }, + ) +} \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 376ed9a..83ac161 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -73,7 +73,7 @@ 7天 关闭 获取新的更新 - 版本 0.6.1 现已发布 + 版本 %1$s 现已发布 施工中 正在路上 账户 @@ -111,4 +111,8 @@ 开放源代码许可 https://github.com/Ashinch/ReadYou https://t.me/ReadYouApp + https://api.github.com/repos/Ashinch/ReadYou/releases/latest + 更新日志 + 更新 %1$s MB + 跳过这个版本 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a779f6e..a88331a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -73,7 +73,7 @@ 7d Close Get new updates - Version 0.6.1 has been released + Version %1$s has been released In coding Coming soon Accounts @@ -111,4 +111,8 @@ Open Source Licenses https://github.com/Ashinch/ReadYou https://t.me/ReadYouApp + https://api.github.com/repos/Ashinch/ReadYou/releases/latest + Change Log + Update %1$s MB + Skip This Version \ No newline at end of file