Add update check for GitHub source
This commit is contained in:
		
							parent
							
								
									3c630e8a5b
								
							
						
					
					
						commit
						0cb60d15b2
					
				| @ -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) | ||||
|  | ||||
| @ -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()); | ||||
|  | ||||
							
								
								
									
										23
									
								
								app/src/main/java/me/ash/reader/data/entity/GitHubRelease.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/src/main/java/me/ash/reader/data/entity/GitHubRelease.kt
									
									
									
									
									
										Normal file
									
								
							| @ -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<AssetsItem>? = 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, | ||||
| ) | ||||
| @ -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 = | ||||
|  | ||||
| @ -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<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 | ||||
| } | ||||
| @ -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 | ||||
|                     } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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 | ||||
|  | ||||
| @ -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, | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -12,6 +12,19 @@ import kotlinx.coroutines.runBlocking | ||||
| import java.io.IOException | ||||
| 
 | ||||
| val Context.dataStore: DataStore<Preferences> 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<T> { | ||||
|             get() = booleanPreferencesKey("isFirstLaunch") | ||||
|     } | ||||
| 
 | ||||
|     object NewVersionPublishDate : DataStoreKeys<String>() { | ||||
|         override val key: Preferences.Key<String> | ||||
|             get() = stringPreferencesKey("newVersionPublishDate") | ||||
|     } | ||||
| 
 | ||||
|     object NewVersionLog : DataStoreKeys<String>() { | ||||
|         override val key: Preferences.Key<String> | ||||
|             get() = stringPreferencesKey("newVersionLog") | ||||
|     } | ||||
| 
 | ||||
|     object NewVersionSize : DataStoreKeys<Int>() { | ||||
|         override val key: Preferences.Key<Int> | ||||
|             get() = intPreferencesKey("newVersionSize") | ||||
|     } | ||||
| 
 | ||||
|     object NewVersionDownloadUrl : DataStoreKeys<String>() { | ||||
|         override val key: Preferences.Key<String> | ||||
|             get() = stringPreferencesKey("newVersionDownloadUrl") | ||||
|     } | ||||
| 
 | ||||
|     object NewVersionNumber : DataStoreKeys<String>() { | ||||
|         override val key: Preferences.Key<String> | ||||
|             get() = stringPreferencesKey("newVersionNumber") | ||||
|     } | ||||
| 
 | ||||
|     object SkipVersionNumber : DataStoreKeys<String>() { | ||||
|         override val key: Preferences.Key<String> | ||||
|             get() = stringPreferencesKey("skipVersionNumber") | ||||
|     } | ||||
| 
 | ||||
|     object CurrentAccountId : DataStoreKeys<Int>() { | ||||
|         override val key: Preferences.Key<Int> | ||||
|             get() = intPreferencesKey("currentAccountId") | ||||
|  | ||||
| @ -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) | ||||
|                     } | ||||
|  | ||||
| @ -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( | ||||
|  | ||||
| @ -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 }, | ||||
|     ) | ||||
| } | ||||
|  | ||||
							
								
								
									
										115
									
								
								app/src/main/java/me/ash/reader/ui/page/settings/UpdateDialog.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								app/src/main/java/me/ash/reader/ui/page/settings/UpdateDialog.kt
									
									
									
									
									
										Normal file
									
								
							| @ -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)) | ||||
|             } | ||||
|         }, | ||||
|     ) | ||||
| } | ||||
| @ -73,7 +73,7 @@ | ||||
|     <string name="seven_days">7天</string> | ||||
|     <string name="close">关闭</string> | ||||
|     <string name="get_new_updates">获取新的更新</string> | ||||
|     <string name="get_new_updates_desc">版本 0.6.1 现已发布</string> | ||||
|     <string name="get_new_updates_desc">版本 %1$s 现已发布</string> | ||||
|     <string name="in_coding">施工中</string> | ||||
|     <string name="coming_soon">正在路上</string> | ||||
|     <string name="accounts">账户</string> | ||||
| @ -111,4 +111,8 @@ | ||||
|     <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="change_log">更新日志</string> | ||||
|     <string name="update">更新 %1$s MB</string> | ||||
|     <string name="skip_this_version">跳过这个版本</string> | ||||
| </resources> | ||||
| @ -73,7 +73,7 @@ | ||||
|     <string name="seven_days">7d</string> | ||||
|     <string name="close">Close</string> | ||||
|     <string name="get_new_updates">Get new updates</string> | ||||
|     <string name="get_new_updates_desc">Version 0.6.1 has been released</string> | ||||
|     <string name="get_new_updates_desc">Version %1$s has been released</string> | ||||
|     <string name="in_coding">In coding</string> | ||||
|     <string name="coming_soon">Coming soon</string> | ||||
|     <string name="accounts">Accounts</string> | ||||
| @ -111,4 +111,8 @@ | ||||
|     <string name="open_source_licenses">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="change_log">Change Log</string> | ||||
|     <string name="update">Update %1$s MB</string> | ||||
|     <string name="skip_this_version">Skip This Version</string> | ||||
| </resources> | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user