Add update check for GitHub source

This commit is contained in:
Ash 2022-04-24 03:39:32 +08:00
parent 3c630e8a5b
commit 0cb60d15b2
15 changed files with 436 additions and 32 deletions

View File

@ -11,6 +11,7 @@ import kotlinx.coroutines.launch
import me.ash.reader.data.module.ApplicationScope import me.ash.reader.data.module.ApplicationScope
import me.ash.reader.data.module.DispatcherDefault import me.ash.reader.data.module.DispatcherDefault
import me.ash.reader.data.repository.* 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.OpmlLocalDataSource
import me.ash.reader.data.source.ReaderDatabase import me.ash.reader.data.source.ReaderDatabase
import me.ash.reader.data.source.RssNetworkDataSource import me.ash.reader.data.source.RssNetworkDataSource
@ -30,6 +31,9 @@ class App : Application(), Configuration.Provider {
@Inject @Inject
lateinit var workManager: WorkManager lateinit var workManager: WorkManager
@Inject
lateinit var appNetworkDataSource: AppNetworkDataSource
@Inject @Inject
lateinit var opmlLocalDataSource: OpmlLocalDataSource lateinit var opmlLocalDataSource: OpmlLocalDataSource
@ -39,6 +43,9 @@ class App : Application(), Configuration.Provider {
@Inject @Inject
lateinit var rssHelper: RssHelper lateinit var rssHelper: RssHelper
@Inject
lateinit var appRepository: AppRepository
@Inject @Inject
lateinit var stringsRepository: StringsRepository lateinit var stringsRepository: StringsRepository
@ -72,6 +79,7 @@ class App : Application(), Configuration.Provider {
applicationScope.launch(dispatcherDefault) { applicationScope.launch(dispatcherDefault) {
accountInit() accountInit()
workerInit() workerInit()
checkUpdate()
} }
} }
@ -90,6 +98,10 @@ class App : Application(), Configuration.Provider {
rssRepository.get().doSync() rssRepository.get().doSync()
} }
private suspend fun checkUpdate() {
appRepository.checkUpdate()
}
override fun getWorkManagerConfiguration(): Configuration = override fun getWorkManagerConfiguration(): Configuration =
Configuration.Builder() Configuration.Builder()
.setWorkerFactory(workerFactory) .setWorkerFactory(workerFactory)

View File

@ -1,6 +1,7 @@
package me.ash.reader package me.ash.reader
import android.content.Context import android.content.Context
import android.os.Looper
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import java.lang.Thread.UncaughtExceptionHandler import java.lang.Thread.UncaughtExceptionHandler
@ -12,7 +13,9 @@ class CrashHandler(private val context: Context) : UncaughtExceptionHandler {
} }
override fun uncaughtException(p0: Thread, p1: Throwable) { override fun uncaughtException(p0: Thread, p1: Throwable) {
Looper.prepare()
Toast.makeText(context, p1.message, Toast.LENGTH_LONG).show() Toast.makeText(context, p1.message, Toast.LENGTH_LONG).show()
Looper.loop()
p1.printStackTrace() p1.printStackTrace()
Log.e("RLog", "uncaughtException: ${p1.message}" ) Log.e("RLog", "uncaughtException: ${p1.message}" )
android.os.Process.killProcess(android.os.Process.myPid()); android.os.Process.killProcess(android.os.Process.myPid());

View 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,
)

View File

@ -4,6 +4,7 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent 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.FeverApiDataSource
import me.ash.reader.data.source.GoogleReaderApiDataSource import me.ash.reader.data.source.GoogleReaderApiDataSource
import me.ash.reader.data.source.RssNetworkDataSource import me.ash.reader.data.source.RssNetworkDataSource
@ -13,6 +14,11 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object RetrofitModule { object RetrofitModule {
@Provides
@Singleton
fun provideAppNetworkDataSource(): AppNetworkDataSource =
AppNetworkDataSource.getInstance()
@Provides @Provides
@Singleton @Singleton
fun provideRssNetworkDataSource(): RssNetworkDataSource = fun provideRssNetworkDataSource(): RssNetworkDataSource =

View File

@ -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
}

View File

@ -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
}
}
}
}
}

View File

@ -31,6 +31,7 @@ fun Banner(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
title: String, title: String,
desc: String? = null, desc: String? = null,
backgroundColor: Color = MaterialTheme.colorScheme.primaryContainer,
icon: ImageVector? = null, icon: ImageVector? = null,
action: (@Composable () -> Unit)? = null, action: (@Composable () -> Unit)? = null,
onClick: () -> Unit = {}, onClick: () -> Unit = {},
@ -46,7 +47,7 @@ fun Banner(
.fillMaxSize() .fillMaxSize()
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
.clip(RoundedCornerShape(32.dp)) .clip(RoundedCornerShape(32.dp))
.background(MaterialTheme.colorScheme.primaryContainer alwaysLight true) .background(backgroundColor alwaysLight true)
.clickable { onClick() } .clickable { onClick() }
.padding(16.dp, 20.dp), .padding(16.dp, 20.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically

View File

@ -2,14 +2,16 @@ package me.ash.reader.ui.component
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.SoundEffectConstants import android.view.SoundEffectConstants
import androidx.compose.material3.Icon import androidx.compose.foundation.layout.size
import androidx.compose.material3.IconButton import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp
@Composable @Composable
fun FeedbackIconButton( fun FeedbackIconButton(
@ -17,6 +19,7 @@ fun FeedbackIconButton(
imageVector: ImageVector, imageVector: ImageVector,
contentDescription: String?, contentDescription: String?,
tint: Color = LocalContentColor.current, tint: Color = LocalContentColor.current,
showBadge: Boolean = false,
isHaptic: Boolean? = true, isHaptic: Boolean? = true,
isSound: Boolean? = true, isSound: Boolean? = true,
onClick: () -> Unit = {}, onClick: () -> Unit = {},
@ -29,6 +32,18 @@ fun FeedbackIconButton(
if (isSound == true) view.playSoundEffect(SoundEffectConstants.CLICK) if (isSound == true) view.playSoundEffect(SoundEffectConstants.CLICK)
onClick() onClick()
}, },
) {
if (showBadge) {
BadgedBox(
badge = {
Badge(
modifier = Modifier
.size(8.dp)
.clip(CircleShape),
containerColor = MaterialTheme.colorScheme.error,
contentColor = MaterialTheme.colorScheme.onError,
)
}
) { ) {
Icon( Icon(
modifier = modifier, modifier = modifier,
@ -37,4 +52,13 @@ fun FeedbackIconButton(
tint = tint, tint = tint,
) )
} }
} else {
Icon(
modifier = modifier,
imageVector = imageVector,
contentDescription = contentDescription,
tint = tint,
)
}
}
} }

View File

@ -12,6 +12,19 @@ import kotlinx.coroutines.runBlocking
import java.io.IOException import java.io.IOException
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings") 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 val Context.isFirstLaunch: Boolean
get() = this.dataStore.get(DataStoreKeys.IsFirstLaunch) ?: true get() = this.dataStore.get(DataStoreKeys.IsFirstLaunch) ?: true
val Context.currentAccountId: Int val Context.currentAccountId: Int
@ -62,6 +75,36 @@ sealed class DataStoreKeys<T> {
get() = booleanPreferencesKey("isFirstLaunch") 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>() { object CurrentAccountId : DataStoreKeys<Int>() {
override val key: Preferences.Key<Int> override val key: Preferences.Key<Int>
get() = intPreferencesKey("currentAccountId") get() = intPreferencesKey("currentAccountId")

View File

@ -1,5 +1,6 @@
package me.ash.reader.ui.page.home.feeds package me.ash.reader.ui.page.home.feeds
import android.annotation.SuppressLint
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.core.* import androidx.compose.animation.core.*
@ -26,15 +27,16 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.work.WorkInfo import androidx.work.WorkInfo
import kotlinx.coroutines.flow.map
import me.ash.reader.R import me.ash.reader.R
import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing 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.Banner
import me.ash.reader.ui.component.DisplayText import me.ash.reader.ui.component.DisplayText
import me.ash.reader.ui.component.FeedbackIconButton import me.ash.reader.ui.component.FeedbackIconButton
import me.ash.reader.ui.component.Subtitle import me.ash.reader.ui.component.Subtitle
import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.*
import me.ash.reader.ui.ext.getDesc
import me.ash.reader.ui.ext.getName
import me.ash.reader.ui.page.common.RouteName import me.ash.reader.ui.page.common.RouteName
import me.ash.reader.ui.page.home.FilterBar import me.ash.reader.ui.page.home.FilterBar
import me.ash.reader.ui.page.home.FilterState 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.SubscribeViewAction
import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewModel import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewModel
@SuppressLint("FlowOperatorInvokedInComposition")
@OptIn( @OptIn(
ExperimentalMaterial3Api::class, com.google.accompanist.pager.ExperimentalPagerApi::class, ExperimentalMaterial3Api::class, com.google.accompanist.pager.ExperimentalPagerApi::class,
androidx.compose.foundation.ExperimentalFoundationApi::class androidx.compose.foundation.ExperimentalFoundationApi::class
@ -59,6 +62,19 @@ fun FeedsPage(
onScrollToPage: (targetPage: Int) -> Unit = {}, onScrollToPage: (targetPage: Int) -> Unit = {},
) { ) {
val context = LocalContext.current 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 viewState = feedsViewModel.viewState.collectAsStateValue()
val owner = LocalLifecycleOwner.current val owner = LocalLifecycleOwner.current
@ -116,6 +132,7 @@ fun FeedsPage(
imageVector = Icons.Outlined.Settings, imageVector = Icons.Outlined.Settings,
contentDescription = stringResource(R.string.settings), contentDescription = stringResource(R.string.settings),
tint = MaterialTheme.colorScheme.onSurface, tint = MaterialTheme.colorScheme.onSurface,
showBadge = newVersionNumber > skipVersionNumber,
) { ) {
navController.navigate(RouteName.SETTINGS) navController.navigate(RouteName.SETTINGS)
} }

View File

@ -8,6 +8,7 @@
package me.ash.reader.ui.page.settings package me.ash.reader.ui.page.settings
import android.view.SoundEffectConstants
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column 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.draw.alpha
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@ -38,8 +40,15 @@ fun SelectableSettingGroupItem(
icon: ImageVector? = null, icon: ImageVector? = null,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
val view = LocalView.current
Surface( 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, color = Color.Unspecified,
) { ) {
Row( Row(

View File

@ -1,33 +1,54 @@
package me.ash.reader.ui.page.settings package me.ash.reader.ui.page.settings
import android.annotation.SuppressLint
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.* import androidx.compose.material.icons.outlined.*
import androidx.compose.material.icons.rounded.ArrowBack import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Close
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import kotlinx.coroutines.flow.map
import me.ash.reader.R 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.Banner
import me.ash.reader.ui.component.DisplayText import me.ash.reader.ui.component.DisplayText
import me.ash.reader.ui.component.FeedbackIconButton 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.page.common.RouteName
import me.ash.reader.ui.theme.palette.onLight import me.ash.reader.ui.theme.palette.onLight
@SuppressLint("FlowOperatorInvokedInComposition")
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SettingsPage( fun SettingsPage(
navController: NavHostController, 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( Scaffold(
modifier = Modifier modifier = Modifier
.background(MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface) .background(MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface)
@ -56,9 +77,15 @@ fun SettingsPage(
DisplayText(text = stringResource(R.string.settings), desc = "") DisplayText(text = stringResource(R.string.settings), desc = "")
} }
item { item {
Box {
if (newVersionNumber > skipVersionNumber) {
Banner( Banner(
title = stringResource(R.string.in_coding), modifier = Modifier.zIndex(1f),
desc = stringResource(R.string.coming_soon), title = stringResource(R.string.get_new_updates),
desc = stringResource(
R.string.get_new_updates_desc,
newVersionNumber.joinToString(".")
),
icon = Icons.Outlined.Lightbulb, icon = Icons.Outlined.Lightbulb,
action = { action = {
Icon( Icon(
@ -66,7 +93,14 @@ fun SettingsPage(
contentDescription = stringResource(R.string.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)) Spacer(modifier = Modifier.height(16.dp))
} }
item { item {
@ -114,4 +148,9 @@ fun SettingsPage(
} }
} }
) )
UpdateDialog(
visible = updateDialogVisible,
onDismissRequest = { updateDialogVisible = false },
)
} }

View 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))
}
},
)
}

View File

@ -73,7 +73,7 @@
<string name="seven_days">7天</string> <string name="seven_days">7天</string>
<string name="close">关闭</string> <string name="close">关闭</string>
<string name="get_new_updates">获取新的更新</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="in_coding">施工中</string>
<string name="coming_soon">正在路上</string> <string name="coming_soon">正在路上</string>
<string name="accounts">账户</string> <string name="accounts">账户</string>
@ -111,4 +111,8 @@
<string name="open_source_licenses">开放源代码许可</string> <string name="open_source_licenses">开放源代码许可</string>
<string name="github_link">https://github.com/Ashinch/ReadYou</string> <string name="github_link">https://github.com/Ashinch/ReadYou</string>
<string name="telegram_link">https://t.me/ReadYouApp</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> </resources>

View File

@ -73,7 +73,7 @@
<string name="seven_days">7d</string> <string name="seven_days">7d</string>
<string name="close">Close</string> <string name="close">Close</string>
<string name="get_new_updates">Get new updates</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="in_coding">In coding</string>
<string name="coming_soon">Coming soon</string> <string name="coming_soon">Coming soon</string>
<string name="accounts">Accounts</string> <string name="accounts">Accounts</string>
@ -111,4 +111,8 @@
<string name="open_source_licenses">Open Source Licenses</string> <string name="open_source_licenses">Open Source Licenses</string>
<string name="github_link">https://github.com/Ashinch/ReadYou</string> <string name="github_link">https://github.com/Ashinch/ReadYou</string>
<string name="telegram_link">https://t.me/ReadYouApp</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> </resources>