diff --git a/app/src/main/java/me/ash/reader/CrashHandler.kt b/app/src/main/java/me/ash/reader/CrashHandler.kt index 73f05f6..0a289fe 100644 --- a/app/src/main/java/me/ash/reader/CrashHandler.kt +++ b/app/src/main/java/me/ash/reader/CrashHandler.kt @@ -7,11 +7,18 @@ import me.ash.reader.ui.ext.showToastLong import java.lang.Thread.UncaughtExceptionHandler import kotlin.system.exitProcess +/** + * The uncaught exception handler for the application. + */ class CrashHandler(private val context: Context) : UncaughtExceptionHandler { + init { Thread.setDefaultUncaughtExceptionHandler(this) } + /** + * Catch all uncaught exception and log it. + */ override fun uncaughtException(p0: Thread, p1: Throwable) { Looper.myLooper() ?: Looper.prepare() context.showToastLong(p1.message) diff --git a/app/src/main/java/me/ash/reader/MainActivity.kt b/app/src/main/java/me/ash/reader/MainActivity.kt index d85979e..b42a312 100644 --- a/app/src/main/java/me/ash/reader/MainActivity.kt +++ b/app/src/main/java/me/ash/reader/MainActivity.kt @@ -10,14 +10,18 @@ import androidx.profileinstaller.ProfileInstallerInitializer import coil.ImageLoader import coil.compose.LocalImageLoader import dagger.hilt.android.AndroidEntryPoint -import me.ash.reader.data.preference.LanguagesPreference -import me.ash.reader.data.preference.SettingsProvider +import me.ash.reader.data.model.preference.LanguagesPreference +import me.ash.reader.data.model.preference.SettingsProvider import me.ash.reader.ui.ext.languages import me.ash.reader.ui.page.common.HomeEntry import javax.inject.Inject +/** + * The Single-Activity Architecture. + */ @AndroidEntryPoint class MainActivity : ComponentActivity() { + @Inject lateinit var imageLoader: ImageLoader @@ -42,4 +46,4 @@ class MainActivity : ComponentActivity() { } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/RYApp.kt b/app/src/main/java/me/ash/reader/RYApp.kt index 80555c4..66d8b36 100644 --- a/app/src/main/java/me/ash/reader/RYApp.kt +++ b/app/src/main/java/me/ash/reader/RYApp.kt @@ -9,8 +9,9 @@ import dagger.hilt.android.HiltAndroidApp import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import me.ash.reader.data.module.ApplicationScope -import me.ash.reader.data.module.DispatcherDefault +import me.ash.reader.data.module.IODispatcher import me.ash.reader.data.repository.* import me.ash.reader.data.source.OpmlLocalDataSource import me.ash.reader.data.source.RYDatabase @@ -21,16 +22,23 @@ import org.conscrypt.Conscrypt import java.security.Security import javax.inject.Inject +/** + * The Application class, where the Dagger components is generated. + */ @HiltAndroidApp class RYApp : Application(), Configuration.Provider { + + /** + * From: [Feeder](https://gitlab.com/spacecowboy/Feeder). + * + * Install Conscrypt to handle TLSv1.3 pre Android10. + */ init { - // From: https://gitlab.com/spacecowboy/Feeder - // Install Conscrypt to handle TLSv1.3 pre Android10 Security.insertProviderAt(Conscrypt.newProvider(), 1) } @Inject - lateinit var RYDatabase: RYDatabase + lateinit var ryDatabase: RYDatabase @Inject lateinit var workerFactory: HiltWorkerFactory @@ -39,7 +47,7 @@ class RYApp : Application(), Configuration.Provider { lateinit var workManager: WorkManager @Inject - lateinit var RYNetworkDataSource: RYNetworkDataSource + lateinit var ryNetworkDataSource: RYNetworkDataSource @Inject lateinit var opmlLocalDataSource: OpmlLocalDataSource @@ -73,8 +81,8 @@ class RYApp : Application(), Configuration.Provider { lateinit var applicationScope: CoroutineScope @Inject - @DispatcherDefault - lateinit var dispatcherDefault: CoroutineDispatcher + @IODispatcher + lateinit var ioDispatcher: CoroutineDispatcher @Inject lateinit var okHttpClient: OkHttpClient @@ -82,27 +90,40 @@ class RYApp : Application(), Configuration.Provider { @Inject lateinit var imageLoader: ImageLoader + /** + * When the application startup. + * + * 1. Set the uncaught exception handler + * 2. Initialize the default account if there is none + * 3. Synchronize once + * 4. Check for new version + */ override fun onCreate() { super.onCreate() CrashHandler(this) - dataStoreInit() - applicationScope.launch(dispatcherDefault) { + applicationScope.launch { accountInit() workerInit() - if (notFdroid) { - checkUpdate() - } + if (notFdroid) checkUpdate() } } - private fun dataStoreInit() { - } + /** + * Override the [Configuration.Builder] to provide the [HiltWorkerFactory]. + */ + override fun getWorkManagerConfiguration(): Configuration = + Configuration.Builder() + .setWorkerFactory(workerFactory) + .setMinimumLoggingLevel(android.util.Log.DEBUG) + .build() private suspend fun accountInit() { - if (accountRepository.isNoAccount()) { - val account = accountRepository.addDefaultAccount() - applicationContext.dataStore.put(DataStoreKeys.CurrentAccountId, account.id!!) - applicationContext.dataStore.put(DataStoreKeys.CurrentAccountType, account.type) + withContext(ioDispatcher) { + if (accountRepository.isNoAccount()) { + val account = accountRepository.addDefaultAccount() + applicationContext.dataStore.put(DataStoreKeys.CurrentAccountId, account.id!!) + applicationContext.dataStore.put(DataStoreKeys.CurrentAccountType, account.type.id) + } } } @@ -111,17 +132,11 @@ class RYApp : Application(), Configuration.Provider { } private suspend fun checkUpdate() { - applicationContext.getLatestApk().let { - if (it.exists()) { - it.del() + withContext(ioDispatcher) { + applicationContext.getLatestApk().let { + if (it.exists()) it.del() } } ryRepository.checkUpdate(showToast = false) } - - override fun getWorkManagerConfiguration(): Configuration = - Configuration.Builder() - .setWorkerFactory(workerFactory) - .setMinimumLoggingLevel(android.util.Log.DEBUG) - .build() } diff --git a/app/src/main/java/me/ash/reader/data/constant/ElevationTokens.kt b/app/src/main/java/me/ash/reader/data/constant/ElevationTokens.kt index 4c240e1..bdce6d8 100644 --- a/app/src/main/java/me/ash/reader/data/constant/ElevationTokens.kt +++ b/app/src/main/java/me/ash/reader/data/constant/ElevationTokens.kt @@ -1,10 +1,16 @@ package me.ash.reader.data.constant +/** + * The tonal elevation tokens. + * + * @see androidx.compose.material3.tokens.ElevationTokens + */ object ElevationTokens { + const val Level0 = 0 const val Level1 = 1 const val Level2 = 3 const val Level3 = 6 const val Level4 = 8 const val Level5 = 12 -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/dao/AccountDao.kt b/app/src/main/java/me/ash/reader/data/dao/AccountDao.kt index 67fe06c..e16b10b 100644 --- a/app/src/main/java/me/ash/reader/data/dao/AccountDao.kt +++ b/app/src/main/java/me/ash/reader/data/dao/AccountDao.kt @@ -1,10 +1,11 @@ package me.ash.reader.data.dao import androidx.room.* -import me.ash.reader.data.entity.Account +import me.ash.reader.data.model.account.Account @Dao interface AccountDao { + @Query( """ SELECT * FROM account @@ -31,4 +32,4 @@ interface AccountDao { @Delete suspend fun delete(vararg account: Account) -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/dao/ArticleDao.kt b/app/src/main/java/me/ash/reader/data/dao/ArticleDao.kt index 7505620..bdc08b4 100644 --- a/app/src/main/java/me/ash/reader/data/dao/ArticleDao.kt +++ b/app/src/main/java/me/ash/reader/data/dao/ArticleDao.kt @@ -3,13 +3,14 @@ package me.ash.reader.data.dao import androidx.paging.PagingSource import androidx.room.* import kotlinx.coroutines.flow.Flow -import me.ash.reader.data.entity.Article -import me.ash.reader.data.entity.ArticleWithFeed -import me.ash.reader.data.model.ImportantCount +import me.ash.reader.data.model.article.Article +import me.ash.reader.data.model.article.ArticleWithFeed +import me.ash.reader.data.model.feed.ImportantNum import java.util.* @Dao interface ArticleDao { + @Transaction @Query( """ @@ -298,8 +299,8 @@ interface ArticleDao { ) fun queryImportantCountWhenIsUnread( accountId: Int, - isUnread: Boolean - ): Flow> + isUnread: Boolean, + ): Flow> @Transaction @Query( @@ -315,8 +316,8 @@ interface ArticleDao { ) fun queryImportantCountWhenIsStarred( accountId: Int, - isStarred: Boolean - ): Flow> + isStarred: Boolean, + ): Flow> @Transaction @Query( @@ -329,7 +330,7 @@ interface ArticleDao { GROUP BY a.feedId """ ) - fun queryImportantCountWhenIsAll(accountId: Int): Flow> + fun queryImportantCountWhenIsAll(accountId: Int): Flow> @Transaction @Query( @@ -352,7 +353,7 @@ interface ArticleDao { ) fun queryArticleWithFeedWhenIsStarred( accountId: Int, - isStarred: Boolean + isStarred: Boolean, ): PagingSource @Transaction @@ -366,7 +367,7 @@ interface ArticleDao { ) fun queryArticleWithFeedWhenIsUnread( accountId: Int, - isUnread: Boolean + isUnread: Boolean, ): PagingSource @Transaction @@ -444,7 +445,7 @@ interface ArticleDao { ) fun queryArticleWithFeedByFeedIdWhenIsAll( accountId: Int, - feedId: String + feedId: String, ): PagingSource @Transaction @@ -536,4 +537,4 @@ interface ArticleDao { insertList(it) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/dao/FeedDao.kt b/app/src/main/java/me/ash/reader/data/dao/FeedDao.kt index 14b35fa..4f5e607 100644 --- a/app/src/main/java/me/ash/reader/data/dao/FeedDao.kt +++ b/app/src/main/java/me/ash/reader/data/dao/FeedDao.kt @@ -1,10 +1,11 @@ package me.ash.reader.data.dao import androidx.room.* -import me.ash.reader.data.entity.Feed +import me.ash.reader.data.model.feed.Feed @Dao interface FeedDao { + @Query( """ UPDATE feed SET groupId = :targetGroupId @@ -15,7 +16,7 @@ interface FeedDao { suspend fun updateTargetGroupIdByGroupId( accountId: Int, groupId: String, - targetGroupId: String + targetGroupId: String, ) @Query( @@ -28,7 +29,7 @@ interface FeedDao { suspend fun updateIsFullContentByGroupId( accountId: Int, groupId: String, - isFullContent: Boolean + isFullContent: Boolean, ) @Query( @@ -41,7 +42,7 @@ interface FeedDao { suspend fun updateIsNotificationByGroupId( accountId: Int, groupId: String, - isNotification: Boolean + isNotification: Boolean, ) @Query( @@ -89,4 +90,4 @@ interface FeedDao { @Delete suspend fun delete(vararg feed: Feed) -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/dao/GroupDao.kt b/app/src/main/java/me/ash/reader/data/dao/GroupDao.kt index 40927e1..625e447 100644 --- a/app/src/main/java/me/ash/reader/data/dao/GroupDao.kt +++ b/app/src/main/java/me/ash/reader/data/dao/GroupDao.kt @@ -2,11 +2,12 @@ package me.ash.reader.data.dao import androidx.room.* import kotlinx.coroutines.flow.Flow -import me.ash.reader.data.entity.Group -import me.ash.reader.data.entity.GroupWithFeed +import me.ash.reader.data.model.group.Group +import me.ash.reader.data.model.group.GroupWithFeed @Dao interface GroupDao { + @Query( """ SELECT * FROM `group` @@ -57,4 +58,4 @@ interface GroupDao { @Delete suspend fun delete(vararg group: Group) -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/model/ImportantCount.kt b/app/src/main/java/me/ash/reader/data/model/ImportantCount.kt deleted file mode 100644 index 5e6ffc0..0000000 --- a/app/src/main/java/me/ash/reader/data/model/ImportantCount.kt +++ /dev/null @@ -1,7 +0,0 @@ -package me.ash.reader.data.model - -data class ImportantCount( - val important: Int, - val feedId: String, - val groupId: String, -) diff --git a/app/src/main/java/me/ash/reader/data/model/LatestRelease.kt b/app/src/main/java/me/ash/reader/data/model/LatestRelease.kt deleted file mode 100644 index ff18b3c..0000000 --- a/app/src/main/java/me/ash/reader/data/model/LatestRelease.kt +++ /dev/null @@ -1,2 +0,0 @@ -package me.ash.reader.data.model - diff --git a/app/src/main/java/me/ash/reader/data/model/Version.kt b/app/src/main/java/me/ash/reader/data/model/Version.kt deleted file mode 100644 index 50ef322..0000000 --- a/app/src/main/java/me/ash/reader/data/model/Version.kt +++ /dev/null @@ -1,32 +0,0 @@ -package me.ash.reader.data.model - -class Version(identifiers: List) { - private var major: Int = 0 - private var minor: Int = 0 - private var point: Int = 0 - - init { - major = identifiers.getOrNull(0)?.toIntOrNull() ?: 0 - minor = identifiers.getOrNull(1)?.toIntOrNull() ?: 0 - point = identifiers.getOrNull(2)?.toIntOrNull() ?: 0 - } - - constructor() : this(listOf()) - constructor(string: String?) : this(string?.split(".") ?: listOf()) - - fun whetherNeedUpdate(current: Version, skip: Version): Boolean = this > current && this > skip - - operator fun compareTo(target: Version): Int = when { - major > target.major -> 1 - major < target.major -> -1 - minor > target.minor -> 1 - minor < target.minor -> -1 - point > target.point -> 1 - point < target.point -> -1 - else -> 0 - } - - override fun toString() = "$major.$minor.$point" -} - -fun String?.toVersion(): Version = Version(this) diff --git a/app/src/main/java/me/ash/reader/data/entity/Account.kt b/app/src/main/java/me/ash/reader/data/model/account/Account.kt similarity index 62% rename from app/src/main/java/me/ash/reader/data/entity/Account.kt rename to app/src/main/java/me/ash/reader/data/model/account/Account.kt index 86e558c..f862faa 100644 --- a/app/src/main/java/me/ash/reader/data/entity/Account.kt +++ b/app/src/main/java/me/ash/reader/data/model/account/Account.kt @@ -1,10 +1,14 @@ -package me.ash.reader.data.entity +package me.ash.reader.data.model.account import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import java.util.* +/** + * In the application, at least one account exists and different accounts + * can have the same feeds and articles. + */ @Entity(tableName = "account") data class Account( @PrimaryKey(autoGenerate = true) @@ -12,13 +16,7 @@ data class Account( @ColumnInfo var name: String, @ColumnInfo - var type: Int, + var type: AccountType, @ColumnInfo var updateAt: Date? = null, -) { - object Type { - const val LOCAL = 1 - const val FEVER = 2 - const val GOOGLE_READER = 3 - } -} \ No newline at end of file +) diff --git a/app/src/main/java/me/ash/reader/data/model/account/AccountType.kt b/app/src/main/java/me/ash/reader/data/model/account/AccountType.kt new file mode 100644 index 0000000..a7ebcec --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/account/AccountType.kt @@ -0,0 +1,45 @@ +package me.ash.reader.data.model.account + +import androidx.room.RoomDatabase +import androidx.room.TypeConverter + +/** + * Each account will specify its local or third-party API type. + */ +class AccountType(val id: Int) { + + /** + * Make sure the constructed object is valid. + */ + init { + if (id < 1 || id > 3) { + throw IllegalArgumentException("Account type id is not valid.") + } + } + + /** + * Type of account currently supported. + */ + companion object { + + val Local = AccountType(1) + val Fever = AccountType(2) + val GoogleReader = AccountType(3) + } +} + +/** + * Provide [TypeConverter] of [AccountType] for [RoomDatabase]. + */ +class AccountTypeConverters { + + @TypeConverter + fun toAccountType(id: Int): AccountType { + return AccountType(id) + } + + @TypeConverter + fun fromAccountType(accountType: AccountType): Int { + return accountType.id + } +} diff --git a/app/src/main/java/me/ash/reader/data/entity/Article.kt b/app/src/main/java/me/ash/reader/data/model/article/Article.kt similarity index 89% rename from app/src/main/java/me/ash/reader/data/entity/Article.kt rename to app/src/main/java/me/ash/reader/data/model/article/Article.kt index 30086fc..723b2a9 100644 --- a/app/src/main/java/me/ash/reader/data/entity/Article.kt +++ b/app/src/main/java/me/ash/reader/data/model/article/Article.kt @@ -1,8 +1,12 @@ -package me.ash.reader.data.entity +package me.ash.reader.data.model.article import androidx.room.* +import me.ash.reader.data.model.feed.Feed import java.util.* +/** + * TODO: Add class description + */ @Entity( tableName = "article", foreignKeys = [ForeignKey( @@ -43,6 +47,7 @@ data class Article( @ColumnInfo(defaultValue = "false") var isReadLater: Boolean = false, ) { + @Ignore var dateString: String? = null -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/model/article/ArticleFlowItem.kt b/app/src/main/java/me/ash/reader/data/model/article/ArticleFlowItem.kt new file mode 100644 index 0000000..bf456ed --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/article/ArticleFlowItem.kt @@ -0,0 +1,51 @@ +package me.ash.reader.data.model.article + +import androidx.paging.PagingData +import androidx.paging.insertSeparators +import androidx.paging.map +import me.ash.reader.data.repository.StringsRepository + +/** + * Provide paginated and inserted separator data types for article list view. + * + * @see me.ash.reader.ui.page.home.flow.ArticleList + */ +sealed class ArticleFlowItem { + + /** + * The [Article] item. + * + * @see me.ash.reader.ui.page.home.flow.ArticleItem + */ + class Article(val articleWithFeed: ArticleWithFeed) : ArticleFlowItem() + + /** + * The feed publication date separator between [Article] items. + * + * @see me.ash.reader.ui.page.home.flow.StickyHeader + */ + class Date(val date: String, val showSpacer: Boolean) : ArticleFlowItem() +} + +/** + * Mapping [ArticleWithFeed] list to [ArticleFlowItem] list. + */ +fun PagingData.mapPagingFlowItem(stringsRepository: StringsRepository): PagingData = + map { + ArticleFlowItem.Article(it.apply { + article.dateString = stringsRepository.formatAsString( + date = article.date, + onlyHourMinute = true + ) + }) + }.insertSeparators { before, after -> + val beforeDate = + stringsRepository.formatAsString(before?.articleWithFeed?.article?.date) + val afterDate = + stringsRepository.formatAsString(after?.articleWithFeed?.article?.date) + if (beforeDate != afterDate) { + afterDate?.let { ArticleFlowItem.Date(it, beforeDate != null) } + } else { + null + } + } diff --git a/app/src/main/java/me/ash/reader/data/entity/ArticleWithFeed.kt b/app/src/main/java/me/ash/reader/data/model/article/ArticleWithFeed.kt similarity index 62% rename from app/src/main/java/me/ash/reader/data/entity/ArticleWithFeed.kt rename to app/src/main/java/me/ash/reader/data/model/article/ArticleWithFeed.kt index c3ccc6f..a32dfa0 100644 --- a/app/src/main/java/me/ash/reader/data/entity/ArticleWithFeed.kt +++ b/app/src/main/java/me/ash/reader/data/model/article/ArticleWithFeed.kt @@ -1,8 +1,12 @@ -package me.ash.reader.data.entity +package me.ash.reader.data.model.article import androidx.room.Embedded import androidx.room.Relation +import me.ash.reader.data.model.feed.Feed +/** + * An [article] contains a [feed]. + */ data class ArticleWithFeed( @Embedded var article: Article, diff --git a/app/src/main/java/me/ash/reader/data/entity/Feed.kt b/app/src/main/java/me/ash/reader/data/model/feed/Feed.kt similarity index 94% rename from app/src/main/java/me/ash/reader/data/entity/Feed.kt rename to app/src/main/java/me/ash/reader/data/model/feed/Feed.kt index 8dafacf..d8adc18 100644 --- a/app/src/main/java/me/ash/reader/data/entity/Feed.kt +++ b/app/src/main/java/me/ash/reader/data/model/feed/Feed.kt @@ -1,7 +1,11 @@ -package me.ash.reader.data.entity +package me.ash.reader.data.model.feed import androidx.room.* +import me.ash.reader.data.model.group.Group +/** + * TODO: Add class description + */ @Entity( tableName = "feed", foreignKeys = [ForeignKey( @@ -30,6 +34,7 @@ data class Feed( @ColumnInfo(defaultValue = "false") var isFullContent: Boolean = false, ) { + @Ignore var important: Int? = 0 diff --git a/app/src/main/java/me/ash/reader/data/entity/FeedWithArticle.kt b/app/src/main/java/me/ash/reader/data/model/feed/FeedWithArticle.kt similarity index 52% rename from app/src/main/java/me/ash/reader/data/entity/FeedWithArticle.kt rename to app/src/main/java/me/ash/reader/data/model/feed/FeedWithArticle.kt index f7e77ac..75d6d84 100644 --- a/app/src/main/java/me/ash/reader/data/entity/FeedWithArticle.kt +++ b/app/src/main/java/me/ash/reader/data/model/feed/FeedWithArticle.kt @@ -1,11 +1,15 @@ -package me.ash.reader.data.entity +package me.ash.reader.data.model.feed import androidx.room.Embedded import androidx.room.Relation +import me.ash.reader.data.model.article.Article +/** + * A [feed] contains many [articles]. + */ data class FeedWithArticle( @Embedded var feed: Feed, @Relation(parentColumn = "id", entityColumn = "feedId") - var articles: List
+ var articles: List
, ) diff --git a/app/src/main/java/me/ash/reader/data/entity/FeedWithGroup.kt b/app/src/main/java/me/ash/reader/data/model/feed/FeedWithGroup.kt similarity index 56% rename from app/src/main/java/me/ash/reader/data/entity/FeedWithGroup.kt rename to app/src/main/java/me/ash/reader/data/model/feed/FeedWithGroup.kt index bf254b7..b54f6cd 100644 --- a/app/src/main/java/me/ash/reader/data/entity/FeedWithGroup.kt +++ b/app/src/main/java/me/ash/reader/data/model/feed/FeedWithGroup.kt @@ -1,11 +1,15 @@ -package me.ash.reader.data.entity +package me.ash.reader.data.model.feed import androidx.room.Embedded import androidx.room.Relation +import me.ash.reader.data.model.group.Group +/** + * A [feed] contains a [group]. + */ data class FeedWithGroup( @Embedded var feed: Feed, @Relation(parentColumn = "groupId", entityColumn = "id") - var group: Group + var group: Group, ) diff --git a/app/src/main/java/me/ash/reader/data/model/feed/ImportantNum.kt b/app/src/main/java/me/ash/reader/data/model/feed/ImportantNum.kt new file mode 100644 index 0000000..fbeb1e6 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/feed/ImportantNum.kt @@ -0,0 +1,15 @@ +package me.ash.reader.data.model.feed + +/** + * Counting the [important] number of articles in feeds and groups is generally + * used in three situations. + * + * - Unread: Articles that have not been read yet + * - Starred: Articles that have been marked as starred + * - All: All articles + */ +data class ImportantNum( + val important: Int, + val feedId: String, + val groupId: String, +) diff --git a/app/src/main/java/me/ash/reader/data/model/Filter.kt b/app/src/main/java/me/ash/reader/data/model/general/Filter.kt similarity index 59% rename from app/src/main/java/me/ash/reader/data/model/Filter.kt rename to app/src/main/java/me/ash/reader/data/model/general/Filter.kt index 68f8447..691d6bc 100644 --- a/app/src/main/java/me/ash/reader/data/model/Filter.kt +++ b/app/src/main/java/me/ash/reader/data/model/general/Filter.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.model +package me.ash.reader.data.model.general import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FiberManualRecord @@ -13,17 +13,46 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import me.ash.reader.R +import me.ash.reader.data.model.general.Filter.Companion.All +import me.ash.reader.data.model.general.Filter.Companion.Starred +import me.ash.reader.data.model.general.Filter.Companion.Unread -class Filter( +/** + * Indicates filter conditions. + * + * - [All]: all items + * - [Unread]: unread items + * - [Starred]: starred items + */ +class Filter private constructor( val index: Int, val iconOutline: ImageVector, val iconFilled: ImageVector, ) { + fun isStarred(): Boolean = this == Starred fun isUnread(): Boolean = this == Unread fun isAll(): Boolean = this == All + @Stable + @Composable + fun toName(): String = when (this) { + Unread -> stringResource(R.string.unread) + Starred -> stringResource(R.string.starred) + else -> stringResource(R.string.all) + } + + @OptIn(ExperimentalComposeUiApi::class) + @Stable + @Composable + fun toDesc(important: Int): String = when (this) { + Starred -> pluralStringResource(R.plurals.starred_desc, important, important) + Unread -> pluralStringResource(R.plurals.unread_desc, important, important) + else -> pluralStringResource(R.plurals.all_desc, important, important) + } + companion object { + val Starred = Filter( index = 0, iconOutline = Icons.Rounded.StarOutline, @@ -42,20 +71,3 @@ class Filter( val values = listOf(Starred, Unread, All) } } - -@Stable -@Composable -fun Filter.getName(): String = when (this) { - Filter.Unread -> stringResource(R.string.unread) - Filter.Starred -> stringResource(R.string.starred) - else -> stringResource(R.string.all) -} - -@OptIn(ExperimentalComposeUiApi::class) -@Stable -@Composable -fun Filter.getDesc(important: Int): String = when (this) { - Filter.Starred -> pluralStringResource(R.plurals.starred_desc, important, important) - Filter.Unread -> pluralStringResource(R.plurals.unread_desc, important, important) - else -> pluralStringResource(R.plurals.all_desc, important, important) -} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/model/general/MarkAsReadConditions.kt b/app/src/main/java/me/ash/reader/data/model/general/MarkAsReadConditions.kt new file mode 100644 index 0000000..6ed92b2 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/general/MarkAsReadConditions.kt @@ -0,0 +1,33 @@ +package me.ash.reader.data.model.general + +import me.ash.reader.data.model.general.MarkAsReadConditions.* +import java.util.* + +/** + * Mark as read conditions. + * + * - [SevenDays]: Mark as read if more than 7 days old + * - [ThreeDays]: Mark as read if more than 3 days old + * - [OneDay]: Mark as read if more than 1 day old + * - [All]: Mark all as read + */ +enum class MarkAsReadConditions { + SevenDays, + ThreeDays, + OneDay, + All, + ; + + fun toDate(): Date? = when (this) { + All -> null + else -> Calendar.getInstance().apply { + time = Date() + add(Calendar.DAY_OF_MONTH, when (this@MarkAsReadConditions) { + SevenDays -> -7 + ThreeDays -> -3 + OneDay -> -1 + else -> throw IllegalArgumentException("Unknown condition: $this") + }) + }.time + } +} diff --git a/app/src/main/java/me/ash/reader/data/model/general/Version.kt b/app/src/main/java/me/ash/reader/data/model/general/Version.kt new file mode 100644 index 0000000..f33b6be --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/general/Version.kt @@ -0,0 +1,51 @@ +package me.ash.reader.data.model.general + +/** + * Application version number, consisting of three fields. + * + * - [major]: The major version number, such as 1 + * - [minor]: The major version number, such as 2 + * - [point]: The major version number, such as 3 (if converted to a string, + * the value is: "1.2.3") + */ +class Version(numbers: List) { + + private var major: Int = 0 + private var minor: Int = 0 + private var point: Int = 0 + + init { + major = numbers.getOrNull(0)?.toIntOrNull() ?: 0 + minor = numbers.getOrNull(1)?.toIntOrNull() ?: 0 + point = numbers.getOrNull(2)?.toIntOrNull() ?: 0 + } + + constructor() : this(listOf()) + constructor(string: String?) : this(string?.split(".") ?: listOf()) + + override fun toString() = "$major.$minor.$point" + + /** + * Use [major], [minor], [point] for comparison. + * + * 1. [major] <=> [other.major] + * 2. [minor] <=> [other.minor] + * 3. [point] <=> [other.point] + */ + operator fun compareTo(other: Version): Int = when { + major > other.major -> 1 + major < other.major -> -1 + minor > other.minor -> 1 + minor < other.minor -> -1 + point > other.point -> 1 + point < other.point -> -1 + else -> 0 + } + + /** + * Returns whether this version is larger [current] version and [skip] version. + */ + fun whetherNeedUpdate(current: Version, skip: Version): Boolean = this > current && this > skip +} + +fun String?.toVersion(): Version = Version(this) diff --git a/app/src/main/java/me/ash/reader/data/entity/Group.kt b/app/src/main/java/me/ash/reader/data/model/group/Group.kt similarity index 80% rename from app/src/main/java/me/ash/reader/data/entity/Group.kt rename to app/src/main/java/me/ash/reader/data/model/group/Group.kt index 4929d4b..025a69e 100644 --- a/app/src/main/java/me/ash/reader/data/entity/Group.kt +++ b/app/src/main/java/me/ash/reader/data/model/group/Group.kt @@ -1,10 +1,13 @@ -package me.ash.reader.data.entity +package me.ash.reader.data.model.group import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.Ignore import androidx.room.PrimaryKey +/** + * TODO: Add class description + */ @Entity(tableName = "group") data class Group( @PrimaryKey @@ -14,6 +17,7 @@ data class Group( @ColumnInfo(index = true) var accountId: Int, ) { + @Ignore var important: Int? = 0 -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/entity/GroupWithFeed.kt b/app/src/main/java/me/ash/reader/data/model/group/GroupWithFeed.kt similarity index 54% rename from app/src/main/java/me/ash/reader/data/entity/GroupWithFeed.kt rename to app/src/main/java/me/ash/reader/data/model/group/GroupWithFeed.kt index f15efdb..7923054 100644 --- a/app/src/main/java/me/ash/reader/data/entity/GroupWithFeed.kt +++ b/app/src/main/java/me/ash/reader/data/model/group/GroupWithFeed.kt @@ -1,11 +1,15 @@ -package me.ash.reader.data.entity +package me.ash.reader.data.model.group import androidx.room.Embedded import androidx.room.Relation +import me.ash.reader.data.model.feed.Feed +/** + * A [group] contains many [feeds]. + */ data class GroupWithFeed( @Embedded var group: Group, @Relation(parentColumn = "id", entityColumn = "groupId") - var feeds: MutableList + var feeds: MutableList, ) diff --git a/app/src/main/java/me/ash/reader/data/preference/AmoledDarkThemePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/AmoledDarkThemePreference.kt similarity index 95% rename from app/src/main/java/me/ash/reader/data/preference/AmoledDarkThemePreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/AmoledDarkThemePreference.kt index 30e2c85..12cfd87 100644 --- a/app/src/main/java/me/ash/reader/data/preference/AmoledDarkThemePreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/AmoledDarkThemePreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -22,6 +22,7 @@ sealed class AmoledDarkThemePreference(val value: Boolean) : Preference() { } companion object { + val default = OFF val values = listOf(ON, OFF) @@ -38,4 +39,4 @@ operator fun AmoledDarkThemePreference.not(): AmoledDarkThemePreference = when (value) { true -> AmoledDarkThemePreference.OFF false -> AmoledDarkThemePreference.ON - } \ No newline at end of file + } diff --git a/app/src/main/java/me/ash/reader/data/preference/CustomPrimaryColorPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/CustomPrimaryColorPreference.kt similarity index 93% rename from app/src/main/java/me/ash/reader/data/preference/CustomPrimaryColorPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/CustomPrimaryColorPreference.kt index ad5d157..7762f5f 100644 --- a/app/src/main/java/me/ash/reader/data/preference/CustomPrimaryColorPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/CustomPrimaryColorPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -9,6 +9,7 @@ import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.put object CustomPrimaryColorPreference { + const val default = "" fun put(context: Context, scope: CoroutineScope, value: String) { @@ -19,4 +20,4 @@ object CustomPrimaryColorPreference { fun fromPreferences(preferences: Preferences) = preferences[DataStoreKeys.CustomPrimaryColor.key] ?: default -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/DarkThemePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/DarkThemePreference.kt similarity index 95% rename from app/src/main/java/me/ash/reader/data/preference/DarkThemePreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/DarkThemePreference.kt index 4047372..48aa8b8 100644 --- a/app/src/main/java/me/ash/reader/data/preference/DarkThemePreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/DarkThemePreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.compose.foundation.isSystemInDarkTheme @@ -26,7 +26,7 @@ sealed class DarkThemePreference(val value: Int) : Preference() { } } - fun getDesc(context: Context): String = + fun toDesc(context: Context): String = when (this) { UseDeviceTheme -> context.getString(R.string.use_device_theme) ON -> context.getString(R.string.on) @@ -42,6 +42,7 @@ sealed class DarkThemePreference(val value: Int) : Preference() { } companion object { + val default = UseDeviceTheme val values = listOf(UseDeviceTheme, ON, OFF) @@ -63,6 +64,7 @@ operator fun DarkThemePreference.not(): DarkThemePreference = } else { DarkThemePreference.ON } + DarkThemePreference.ON -> DarkThemePreference.OFF DarkThemePreference.OFF -> DarkThemePreference.ON - } \ No newline at end of file + } diff --git a/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarFilledPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FeedsFilterBarFilledPreference.kt similarity index 95% rename from app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarFilledPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FeedsFilterBarFilledPreference.kt index 683d1c8..998e093 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarFilledPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FeedsFilterBarFilledPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -22,6 +22,7 @@ sealed class FeedsFilterBarFilledPreference(val value: Boolean) : Preference() { } companion object { + val default = OFF val values = listOf(ON, OFF) @@ -38,4 +39,4 @@ operator fun FeedsFilterBarFilledPreference.not(): FeedsFilterBarFilledPreferenc when (value) { true -> FeedsFilterBarFilledPreference.OFF false -> FeedsFilterBarFilledPreference.ON - } \ No newline at end of file + } diff --git a/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarPaddingPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FeedsFilterBarPaddingPreference.kt similarity index 93% rename from app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarPaddingPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FeedsFilterBarPaddingPreference.kt index b868411..bcc78dc 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarPaddingPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FeedsFilterBarPaddingPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -9,6 +9,7 @@ import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.put object FeedsFilterBarPaddingPreference { + const val default = 60 fun put(context: Context, scope: CoroutineScope, value: Int) { @@ -19,4 +20,4 @@ object FeedsFilterBarPaddingPreference { fun fromPreferences(preferences: Preferences) = preferences[DataStoreKeys.FeedsFilterBarPadding.key] ?: default -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarStylePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FeedsFilterBarStylePreference.kt similarity index 94% rename from app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarStylePreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FeedsFilterBarStylePreference.kt index c553834..22ff15c 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarStylePreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FeedsFilterBarStylePreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -23,7 +23,7 @@ sealed class FeedsFilterBarStylePreference(val value: Int) : Preference() { } } - fun getDesc(context: Context): String = + fun toDesc(context: Context): String = when (this) { Icon -> context.getString(R.string.icons) IconLabel -> context.getString(R.string.icons_and_labels) @@ -31,6 +31,7 @@ sealed class FeedsFilterBarStylePreference(val value: Int) : Preference() { } companion object { + val default = Icon val values = listOf(Icon, IconLabel, IconLabelOnlySelected) @@ -42,4 +43,4 @@ sealed class FeedsFilterBarStylePreference(val value: Int) : Preference() { else -> default } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarTonalElevationPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FeedsFilterBarTonalElevationPreference.kt similarity index 96% rename from app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarTonalElevationPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FeedsFilterBarTonalElevationPreference.kt index ecdd1cc..46cec9f 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarTonalElevationPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FeedsFilterBarTonalElevationPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -26,7 +26,7 @@ sealed class FeedsFilterBarTonalElevationPreference(val value: Int) : Preference } } - fun getDesc(context: Context): String = + fun toDesc(context: Context): String = when (this) { Level0 -> "Level 0 (${ElevationTokens.Level0}dp)" Level1 -> "Level 1 (${ElevationTokens.Level1}dp)" @@ -37,6 +37,7 @@ sealed class FeedsFilterBarTonalElevationPreference(val value: Int) : Preference } companion object { + val default = Level0 val values = listOf(Level0, Level1, Level2, Level3, Level4, Level5) diff --git a/app/src/main/java/me/ash/reader/data/preference/FeedsGroupListExpandPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FeedsGroupListExpandPreference.kt similarity index 95% rename from app/src/main/java/me/ash/reader/data/preference/FeedsGroupListExpandPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FeedsGroupListExpandPreference.kt index 2056631..c21da44 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FeedsGroupListExpandPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FeedsGroupListExpandPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -22,6 +22,7 @@ sealed class FeedsGroupListExpandPreference(val value: Boolean) : Preference() { } companion object { + val default = ON val values = listOf(ON, OFF) @@ -38,4 +39,4 @@ operator fun FeedsGroupListExpandPreference.not(): FeedsGroupListExpandPreferenc when (value) { true -> FeedsGroupListExpandPreference.OFF false -> FeedsGroupListExpandPreference.ON - } \ No newline at end of file + } diff --git a/app/src/main/java/me/ash/reader/data/preference/FeedsGroupListTonalElevationPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FeedsGroupListTonalElevationPreference.kt similarity index 96% rename from app/src/main/java/me/ash/reader/data/preference/FeedsGroupListTonalElevationPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FeedsGroupListTonalElevationPreference.kt index 0fbea55..7626e0f 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FeedsGroupListTonalElevationPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FeedsGroupListTonalElevationPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -26,7 +26,7 @@ sealed class FeedsGroupListTonalElevationPreference(val value: Int) : Preference } } - fun getDesc(context: Context): String = + fun toDesc(context: Context): String = when (this) { Level0 -> "Level 0 (${ElevationTokens.Level0}dp)" Level1 -> "Level 1 (${ElevationTokens.Level1}dp)" @@ -37,6 +37,7 @@ sealed class FeedsGroupListTonalElevationPreference(val value: Int) : Preference } companion object { + val default = Level0 val values = listOf(Level0, Level1, Level2, Level3, Level4, Level5) @@ -51,4 +52,4 @@ sealed class FeedsGroupListTonalElevationPreference(val value: Int) : Preference else -> default } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/FeedsTopBarTonalElevationPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FeedsTopBarTonalElevationPreference.kt similarity index 95% rename from app/src/main/java/me/ash/reader/data/preference/FeedsTopBarTonalElevationPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FeedsTopBarTonalElevationPreference.kt index 9769340..e0a3ae9 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FeedsTopBarTonalElevationPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FeedsTopBarTonalElevationPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -26,7 +26,7 @@ sealed class FeedsTopBarTonalElevationPreference(val value: Int) : Preference() } } - fun getDesc(context: Context): String = + fun toDesc(context: Context): String = when (this) { Level0 -> "Level 0 (${ElevationTokens.Level0}dp)" Level1 -> "Level 1 (${ElevationTokens.Level1}dp)" @@ -37,6 +37,7 @@ sealed class FeedsTopBarTonalElevationPreference(val value: Int) : Preference() } companion object { + val default = Level0 val values = listOf(Level0, Level1, Level2, Level3, Level4, Level5) @@ -51,4 +52,4 @@ sealed class FeedsTopBarTonalElevationPreference(val value: Int) : Preference() else -> default } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListDateStickyHeaderPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListDateStickyHeaderPreference.kt similarity index 96% rename from app/src/main/java/me/ash/reader/data/preference/FlowArticleListDateStickyHeaderPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListDateStickyHeaderPreference.kt index cb7f6c7..d650f09 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListDateStickyHeaderPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListDateStickyHeaderPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -22,6 +22,7 @@ sealed class FlowArticleListDateStickyHeaderPreference(val value: Boolean) : Pre } companion object { + val default = ON val values = listOf(ON, OFF) @@ -38,4 +39,4 @@ operator fun FlowArticleListDateStickyHeaderPreference.not(): FlowArticleListDat when (value) { true -> FlowArticleListDateStickyHeaderPreference.OFF false -> FlowArticleListDateStickyHeaderPreference.ON - } \ No newline at end of file + } diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListDescPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListDescPreference.kt similarity index 95% rename from app/src/main/java/me/ash/reader/data/preference/FlowArticleListDescPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListDescPreference.kt index 8373917..f50ee5f 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListDescPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListDescPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -22,6 +22,7 @@ sealed class FlowArticleListDescPreference(val value: Boolean) : Preference() { } companion object { + val default = ON val values = listOf(ON, OFF) @@ -38,4 +39,4 @@ operator fun FlowArticleListDescPreference.not(): FlowArticleListDescPreference when (value) { true -> FlowArticleListDescPreference.OFF false -> FlowArticleListDescPreference.ON - } \ No newline at end of file + } diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListFeedIconPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListFeedIconPreference.kt similarity index 96% rename from app/src/main/java/me/ash/reader/data/preference/FlowArticleListFeedIconPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListFeedIconPreference.kt index f93708e..3727166 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListFeedIconPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListFeedIconPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -22,6 +22,7 @@ sealed class FlowArticleListFeedIconPreference(val value: Boolean) : Preference( } companion object { + val default = ON val values = listOf(ON, OFF) @@ -38,4 +39,4 @@ operator fun FlowArticleListFeedIconPreference.not(): FlowArticleListFeedIconPre when (value) { true -> FlowArticleListFeedIconPreference.OFF false -> FlowArticleListFeedIconPreference.ON - } \ No newline at end of file + } diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListFeedNamePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListFeedNamePreference.kt similarity index 96% rename from app/src/main/java/me/ash/reader/data/preference/FlowArticleListFeedNamePreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListFeedNamePreference.kt index 994289d..fac433f 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListFeedNamePreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListFeedNamePreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -22,6 +22,7 @@ sealed class FlowArticleListFeedNamePreference(val value: Boolean) : Preference( } companion object { + val default = ON val values = listOf(ON, OFF) @@ -38,4 +39,4 @@ operator fun FlowArticleListFeedNamePreference.not(): FlowArticleListFeedNamePre when (value) { true -> FlowArticleListFeedNamePreference.OFF false -> FlowArticleListFeedNamePreference.ON - } \ No newline at end of file + } diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListImagePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListImagePreference.kt similarity index 95% rename from app/src/main/java/me/ash/reader/data/preference/FlowArticleListImagePreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListImagePreference.kt index ef56d37..526923a 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListImagePreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListImagePreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -22,6 +22,7 @@ sealed class FlowArticleListImagePreference(val value: Boolean) : Preference() { } companion object { + val default = ON val values = listOf(ON, OFF) @@ -38,4 +39,4 @@ operator fun FlowArticleListImagePreference.not(): FlowArticleListImagePreferenc when (value) { true -> FlowArticleListImagePreference.OFF false -> FlowArticleListImagePreference.ON - } \ No newline at end of file + } diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListTimePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListTimePreference.kt similarity index 95% rename from app/src/main/java/me/ash/reader/data/preference/FlowArticleListTimePreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListTimePreference.kt index cbad148..7d59c0f 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListTimePreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListTimePreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -22,6 +22,7 @@ sealed class FlowArticleListTimePreference(val value: Boolean) : Preference() { } companion object { + val default = ON val values = listOf(ON, OFF) @@ -38,4 +39,4 @@ operator fun FlowArticleListTimePreference.not(): FlowArticleListTimePreference when (value) { true -> FlowArticleListTimePreference.OFF false -> FlowArticleListTimePreference.ON - } \ No newline at end of file + } diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListTonalElevationPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListTonalElevationPreference.kt similarity index 96% rename from app/src/main/java/me/ash/reader/data/preference/FlowArticleListTonalElevationPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListTonalElevationPreference.kt index 3ff0592..ee6121f 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListTonalElevationPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FlowArticleListTonalElevationPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -26,7 +26,7 @@ sealed class FlowArticleListTonalElevationPreference(val value: Int) : Preferenc } } - fun getDesc(context: Context): String = + fun toDesc(context: Context): String = when (this) { Level0 -> "Level 0 (${ElevationTokens.Level0}dp)" Level1 -> "Level 1 (${ElevationTokens.Level1}dp)" @@ -37,6 +37,7 @@ sealed class FlowArticleListTonalElevationPreference(val value: Int) : Preferenc } companion object { + val default = Level0 val values = listOf(Level0, Level1, Level2, Level3, Level4, Level5) @@ -51,4 +52,4 @@ sealed class FlowArticleListTonalElevationPreference(val value: Int) : Preferenc else -> default } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarFilledPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FlowFilterBarFilledPreference.kt similarity index 95% rename from app/src/main/java/me/ash/reader/data/preference/FlowFilterBarFilledPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FlowFilterBarFilledPreference.kt index 632d651..99f3c94 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarFilledPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FlowFilterBarFilledPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -22,6 +22,7 @@ sealed class FlowFilterBarFilledPreference(val value: Boolean) : Preference() { } companion object { + val default = OFF val values = listOf(ON, OFF) @@ -38,4 +39,4 @@ operator fun FlowFilterBarFilledPreference.not(): FlowFilterBarFilledPreference when (value) { true -> FlowFilterBarFilledPreference.OFF false -> FlowFilterBarFilledPreference.ON - } \ No newline at end of file + } diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarPaddingPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FlowFilterBarPaddingPreference.kt similarity index 93% rename from app/src/main/java/me/ash/reader/data/preference/FlowFilterBarPaddingPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FlowFilterBarPaddingPreference.kt index b9b08aa..938e1d8 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarPaddingPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FlowFilterBarPaddingPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -9,6 +9,7 @@ import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.put object FlowFilterBarPaddingPreference { + const val default = 60 fun put(context: Context, scope: CoroutineScope, value: Int) { @@ -19,4 +20,4 @@ object FlowFilterBarPaddingPreference { fun fromPreferences(preferences: Preferences) = preferences[DataStoreKeys.FlowFilterBarPadding.key] ?: default -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarStylePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FlowFilterBarStylePreference.kt similarity index 94% rename from app/src/main/java/me/ash/reader/data/preference/FlowFilterBarStylePreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FlowFilterBarStylePreference.kt index a25c9d2..3cf4a90 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarStylePreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FlowFilterBarStylePreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -23,7 +23,7 @@ sealed class FlowFilterBarStylePreference(val value: Int) : Preference() { } } - fun getDesc(context: Context): String = + fun toDesc(context: Context): String = when (this) { Icon -> context.getString(R.string.icons) IconLabel -> context.getString(R.string.icons_and_labels) @@ -31,6 +31,7 @@ sealed class FlowFilterBarStylePreference(val value: Int) : Preference() { } companion object { + val default = Icon val values = listOf(Icon, IconLabel, IconLabelOnlySelected) @@ -42,4 +43,4 @@ sealed class FlowFilterBarStylePreference(val value: Int) : Preference() { else -> default } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarTonalElevationPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FlowFilterBarTonalElevationPreference.kt similarity index 95% rename from app/src/main/java/me/ash/reader/data/preference/FlowFilterBarTonalElevationPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FlowFilterBarTonalElevationPreference.kt index 45fcc5c..d569580 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarTonalElevationPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FlowFilterBarTonalElevationPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -26,7 +26,7 @@ sealed class FlowFilterBarTonalElevationPreference(val value: Int) : Preference( } } - fun getDesc(context: Context): String = + fun toDesc(context: Context): String = when (this) { Level0 -> "Level 0 (${ElevationTokens.Level0}dp)" Level1 -> "Level 1 (${ElevationTokens.Level1}dp)" @@ -37,6 +37,7 @@ sealed class FlowFilterBarTonalElevationPreference(val value: Int) : Preference( } companion object { + val default = Level0 val values = listOf(Level0, Level1, Level2, Level3, Level4, Level5) @@ -51,4 +52,4 @@ sealed class FlowFilterBarTonalElevationPreference(val value: Int) : Preference( else -> default } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowTopBarTonalElevationPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/FlowTopBarTonalElevationPreference.kt similarity index 95% rename from app/src/main/java/me/ash/reader/data/preference/FlowTopBarTonalElevationPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/FlowTopBarTonalElevationPreference.kt index 20ad859..8844646 100644 --- a/app/src/main/java/me/ash/reader/data/preference/FlowTopBarTonalElevationPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/FlowTopBarTonalElevationPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -26,7 +26,7 @@ sealed class FlowTopBarTonalElevationPreference(val value: Int) : Preference() { } } - fun getDesc(context: Context): String = + fun toDesc(context: Context): String = when (this) { Level0 -> "Level 0 (${ElevationTokens.Level0}dp)" Level1 -> "Level 1 (${ElevationTokens.Level1}dp)" @@ -37,6 +37,7 @@ sealed class FlowTopBarTonalElevationPreference(val value: Int) : Preference() { } companion object { + val default = Level0 val values = listOf(Level0, Level1, Level2, Level3, Level4, Level5) @@ -51,4 +52,4 @@ sealed class FlowTopBarTonalElevationPreference(val value: Int) : Preference() { else -> default } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/InitialFilterPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/InitialFilterPreference.kt similarity index 93% rename from app/src/main/java/me/ash/reader/data/preference/InitialFilterPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/InitialFilterPreference.kt index 4f20d2e..c0ff03c 100644 --- a/app/src/main/java/me/ash/reader/data/preference/InitialFilterPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/InitialFilterPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -23,7 +23,7 @@ sealed class InitialFilterPreference(val value: Int) : Preference() { } } - fun getDesc(context: Context): String = + fun toDesc(context: Context): String = when (this) { Starred -> context.getString(R.string.starred) Unread -> context.getString(R.string.unread) @@ -31,6 +31,7 @@ sealed class InitialFilterPreference(val value: Int) : Preference() { } companion object { + val default = All val values = listOf(Starred, Unread, All) @@ -42,4 +43,4 @@ sealed class InitialFilterPreference(val value: Int) : Preference() { else -> default } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/InitialPagePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/InitialPagePreference.kt similarity index 92% rename from app/src/main/java/me/ash/reader/data/preference/InitialPagePreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/InitialPagePreference.kt index fbf954b..253ac3e 100644 --- a/app/src/main/java/me/ash/reader/data/preference/InitialPagePreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/InitialPagePreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -22,13 +22,14 @@ sealed class InitialPagePreference(val value: Int) : Preference() { } } - fun getDesc(context: Context): String = + fun toDesc(context: Context): String = when (this) { FeedsPage -> context.getString(R.string.feeds_page) FlowPage -> context.getString(R.string.flow_page) } companion object { + val default = FeedsPage val values = listOf(FeedsPage, FlowPage) @@ -39,4 +40,4 @@ sealed class InitialPagePreference(val value: Int) : Preference() { else -> default } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/LanguagesPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/LanguagesPreference.kt similarity index 91% rename from app/src/main/java/me/ash/reader/data/preference/LanguagesPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/LanguagesPreference.kt index db11427..422caf9 100644 --- a/app/src/main/java/me/ash/reader/data/preference/LanguagesPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/LanguagesPreference.kt @@ -1,8 +1,7 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import android.os.LocaleList -import android.util.Log import androidx.datastore.preferences.core.Preferences import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -19,6 +18,7 @@ sealed class LanguagesPreference(val value: Int) : Preference() { object German : LanguagesPreference(3) object French : LanguagesPreference(4) object Czech : LanguagesPreference(5) + object Italian : LanguagesPreference(6) override fun put(context: Context, scope: CoroutineScope) { scope.launch { @@ -30,7 +30,7 @@ sealed class LanguagesPreference(val value: Int) : Preference() { } } - fun getDesc(context: Context): String = + fun toDesc(context: Context): String = when (this) { UseDeviceLanguages -> context.getString(R.string.use_device_languages) English -> context.getString(R.string.english) @@ -38,6 +38,7 @@ sealed class LanguagesPreference(val value: Int) : Preference() { German -> context.getString(R.string.german) French -> context.getString(R.string.french) Czech -> context.getString(R.string.czech) + Italian -> context.getString(R.string.italian) } fun getLocale(): Locale = @@ -48,13 +49,11 @@ sealed class LanguagesPreference(val value: Int) : Preference() { German -> Locale("de", "DE") French -> Locale("fr", "FR") Czech -> Locale("cs", "CZ") + Italian -> Locale("it", "IT") } fun setLocale(context: Context) { val locale = getLocale() - - Log.i("Rlog", "setLocale: $locale, ${LocaleList.getDefault().get(0)}") - val resources = context.resources val metrics = resources.displayMetrics val configuration = resources.configuration @@ -73,8 +72,9 @@ sealed class LanguagesPreference(val value: Int) : Preference() { } companion object { + val default = UseDeviceLanguages - val values = listOf(UseDeviceLanguages, English, ChineseSimplified, German, French, Czech) + val values = listOf(UseDeviceLanguages, English, ChineseSimplified, German, French, Czech, Italian) fun fromPreferences(preferences: Preferences): LanguagesPreference = when (preferences[DataStoreKeys.Languages.key]) { @@ -84,6 +84,7 @@ sealed class LanguagesPreference(val value: Int) : Preference() { 3 -> German 4 -> French 5 -> Czech + 6 -> Italian else -> default } @@ -95,7 +96,8 @@ sealed class LanguagesPreference(val value: Int) : Preference() { 3 -> German 4 -> French 5 -> Czech + 6 -> Italian else -> default } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/NewVersionDownloadUrlPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/NewVersionDownloadUrlPreference.kt similarity index 93% rename from app/src/main/java/me/ash/reader/data/preference/NewVersionDownloadUrlPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/NewVersionDownloadUrlPreference.kt index 0b0a5f8..0f75a99 100644 --- a/app/src/main/java/me/ash/reader/data/preference/NewVersionDownloadUrlPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/NewVersionDownloadUrlPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -10,6 +10,7 @@ import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.put object NewVersionDownloadUrlPreference { + const val default = "" fun put(context: Context, scope: CoroutineScope, value: String) { @@ -20,4 +21,4 @@ object NewVersionDownloadUrlPreference { fun fromPreferences(preferences: Preferences) = preferences[DataStoreKeys.NewVersionDownloadUrl.key] ?: default -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/NewVersionLogPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/NewVersionLogPreference.kt similarity index 93% rename from app/src/main/java/me/ash/reader/data/preference/NewVersionLogPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/NewVersionLogPreference.kt index 151e7bf..cf44968 100644 --- a/app/src/main/java/me/ash/reader/data/preference/NewVersionLogPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/NewVersionLogPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -10,6 +10,7 @@ import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.put object NewVersionLogPreference { + const val default = "" fun put(context: Context, scope: CoroutineScope, value: String) { @@ -20,4 +21,4 @@ object NewVersionLogPreference { fun fromPreferences(preferences: Preferences) = preferences[DataStoreKeys.NewVersionLog.key] ?: default -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/NewVersionNumberPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/NewVersionNumberPreference.kt similarity index 82% rename from app/src/main/java/me/ash/reader/data/preference/NewVersionNumberPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/NewVersionNumberPreference.kt index 29424b1..96c5ce1 100644 --- a/app/src/main/java/me/ash/reader/data/preference/NewVersionNumberPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/NewVersionNumberPreference.kt @@ -1,17 +1,18 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import me.ash.reader.data.model.Version -import me.ash.reader.data.model.toVersion +import me.ash.reader.data.model.general.Version +import me.ash.reader.data.model.general.toVersion import me.ash.reader.ui.ext.DataStoreKeys import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.put object NewVersionNumberPreference { + val default = Version() fun put(context: Context, scope: CoroutineScope, value: String) { @@ -22,4 +23,4 @@ object NewVersionNumberPreference { fun fromPreferences(preferences: Preferences) = preferences[DataStoreKeys.NewVersionNumber.key].toVersion() -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/NewVersionPublishDatePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/NewVersionPublishDatePreference.kt similarity index 93% rename from app/src/main/java/me/ash/reader/data/preference/NewVersionPublishDatePreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/NewVersionPublishDatePreference.kt index b7fd390..3218fe3 100644 --- a/app/src/main/java/me/ash/reader/data/preference/NewVersionPublishDatePreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/NewVersionPublishDatePreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -10,6 +10,7 @@ import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.put object NewVersionPublishDatePreference { + const val default = "" fun put(context: Context, scope: CoroutineScope, value: String) { @@ -20,4 +21,4 @@ object NewVersionPublishDatePreference { fun fromPreferences(preferences: Preferences) = preferences[DataStoreKeys.NewVersionPublishDate.key] ?: default -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/NewVersionSizePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/NewVersionSizePreference.kt similarity index 94% rename from app/src/main/java/me/ash/reader/data/preference/NewVersionSizePreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/NewVersionSizePreference.kt index eac3dff..94179e9 100644 --- a/app/src/main/java/me/ash/reader/data/preference/NewVersionSizePreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/NewVersionSizePreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -10,6 +10,7 @@ import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.put object NewVersionSizePreference { + const val default = "" fun Int.formatSize(): String = @@ -25,4 +26,4 @@ object NewVersionSizePreference { fun fromPreferences(preferences: Preferences) = preferences[DataStoreKeys.NewVersionSize.key] ?: default -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/Preference.kt b/app/src/main/java/me/ash/reader/data/model/preference/Preference.kt similarity index 98% rename from app/src/main/java/me/ash/reader/data/preference/Preference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/Preference.kt index e4fba2b..9acac2a 100644 --- a/app/src/main/java/me/ash/reader/data/preference/Preference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/Preference.kt @@ -1,10 +1,11 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences import kotlinx.coroutines.CoroutineScope sealed class Preference { + abstract fun put(context: Context, scope: CoroutineScope) } @@ -50,4 +51,4 @@ fun Preferences.toSettings(): Settings { languages = LanguagesPreference.fromPreferences(this), ) -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/Settings.kt b/app/src/main/java/me/ash/reader/data/model/preference/Settings.kt similarity index 99% rename from app/src/main/java/me/ash/reader/data/preference/Settings.kt rename to app/src/main/java/me/ash/reader/data/model/preference/Settings.kt index b5edcf2..7dd6cec 100644 --- a/app/src/main/java/me/ash/reader/data/preference/Settings.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/Settings.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.util.Log import androidx.compose.runtime.Composable @@ -7,7 +7,7 @@ import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import kotlinx.coroutines.flow.map -import me.ash.reader.data.model.Version +import me.ash.reader.data.model.general.Version import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.dataStore diff --git a/app/src/main/java/me/ash/reader/data/preference/SkipVersionNumberPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/SkipVersionNumberPreference.kt similarity index 82% rename from app/src/main/java/me/ash/reader/data/preference/SkipVersionNumberPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/SkipVersionNumberPreference.kt index d862c42..5924b13 100644 --- a/app/src/main/java/me/ash/reader/data/preference/SkipVersionNumberPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/SkipVersionNumberPreference.kt @@ -1,17 +1,18 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import me.ash.reader.data.model.Version -import me.ash.reader.data.model.toVersion +import me.ash.reader.data.model.general.Version +import me.ash.reader.data.model.general.toVersion import me.ash.reader.ui.ext.DataStoreKeys import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.put object SkipVersionNumberPreference { + val default = Version() fun put(context: Context, scope: CoroutineScope, value: String) { @@ -22,4 +23,4 @@ object SkipVersionNumberPreference { fun fromPreferences(preferences: Preferences) = preferences[DataStoreKeys.SkipVersionNumber.key].toVersion() -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/preference/ThemeIndexPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ThemeIndexPreference.kt similarity index 93% rename from app/src/main/java/me/ash/reader/data/preference/ThemeIndexPreference.kt rename to app/src/main/java/me/ash/reader/data/model/preference/ThemeIndexPreference.kt index 2c947e2..2113fe4 100644 --- a/app/src/main/java/me/ash/reader/data/preference/ThemeIndexPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/ThemeIndexPreference.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.preference +package me.ash.reader.data.model.preference import android.content.Context import androidx.datastore.preferences.core.Preferences @@ -10,6 +10,7 @@ import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.put object ThemeIndexPreference { + const val default = 5 fun put(context: Context, scope: CoroutineScope, value: Int) { @@ -20,4 +21,4 @@ object ThemeIndexPreference { fun fromPreferences(preferences: Preferences) = preferences[DataStoreKeys.ThemeIndex.key] ?: default -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/module/ApplicationScope.kt b/app/src/main/java/me/ash/reader/data/module/ApplicationScope.kt new file mode 100644 index 0000000..dd662f9 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/module/ApplicationScope.kt @@ -0,0 +1,13 @@ +package me.ash.reader.data.module + +import kotlinx.coroutines.CoroutineScope +import javax.inject.Qualifier + +/** + * Provides [CoroutineScope] for the application. + * + * @see CoroutineScopeModule.provideCoroutineScope + */ +@Retention(AnnotationRetention.RUNTIME) +@Qualifier +annotation class ApplicationScope diff --git a/app/src/main/java/me/ash/reader/data/module/CoroutineDispatcherModule.kt b/app/src/main/java/me/ash/reader/data/module/CoroutineDispatcherModule.kt index ea39da8..dc8da99 100644 --- a/app/src/main/java/me/ash/reader/data/module/CoroutineDispatcherModule.kt +++ b/app/src/main/java/me/ash/reader/data/module/CoroutineDispatcherModule.kt @@ -7,23 +7,31 @@ import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers +/** + * Provides global coroutine dispatcher. + * + * - [Dispatchers.Main] + * - [Dispatchers.Main.immediate] + * - [Dispatchers.IO] + * - [Dispatchers.Default] + */ @Module @InstallIn(SingletonComponent::class) object CoroutineDispatcherModule { @Provides - @DispatcherDefault - fun provideDispatcherDefault(): CoroutineDispatcher = Dispatchers.Default + @DefaultDispatcher + fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default @Provides - @DispatcherIO - fun provideDispatcherIO(): CoroutineDispatcher = Dispatchers.IO + @IODispatcher + fun provideIODispatcher(): CoroutineDispatcher = Dispatchers.IO @Provides - @DispatcherMain - fun provideDispatcherMain(): CoroutineDispatcher = Dispatchers.Main + @MainDispatcher + fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main @Provides - @DispatcherMainImmediate - fun provideDispatcherMainImmediate(): CoroutineDispatcher = Dispatchers.Main.immediate + @MainImmediateDispatcher + fun provideMainImmediateDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate } diff --git a/app/src/main/java/me/ash/reader/data/module/CoroutineQualifier.kt b/app/src/main/java/me/ash/reader/data/module/CoroutineQualifier.kt index e24d9cf..71eb31b 100644 --- a/app/src/main/java/me/ash/reader/data/module/CoroutineQualifier.kt +++ b/app/src/main/java/me/ash/reader/data/module/CoroutineQualifier.kt @@ -2,18 +2,30 @@ package me.ash.reader.data.module import javax.inject.Qualifier +/** + * @see CoroutineDispatcherModule.provideDefaultDispatcher + */ @Retention(AnnotationRetention.RUNTIME) @Qualifier -annotation class DispatcherDefault +annotation class DefaultDispatcher +/** + * @see CoroutineDispatcherModule.provideIODispatcher + */ @Retention(AnnotationRetention.RUNTIME) @Qualifier -annotation class DispatcherIO +annotation class IODispatcher +/** + * @see CoroutineDispatcherModule.provideMainDispatcher + */ @Retention(AnnotationRetention.RUNTIME) @Qualifier -annotation class DispatcherMain +annotation class MainDispatcher +/** + * @see CoroutineDispatcherModule.provideMainImmediateDispatcher + */ @Retention(AnnotationRetention.BINARY) @Qualifier -annotation class DispatcherMainImmediate \ No newline at end of file +annotation class MainImmediateDispatcher diff --git a/app/src/main/java/me/ash/reader/data/module/CoroutineScopeModule.kt b/app/src/main/java/me/ash/reader/data/module/CoroutineScopeModule.kt index 543fdae..c2eeef7 100644 --- a/app/src/main/java/me/ash/reader/data/module/CoroutineScopeModule.kt +++ b/app/src/main/java/me/ash/reader/data/module/CoroutineScopeModule.kt @@ -7,13 +7,12 @@ import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob -import javax.inject.Qualifier import javax.inject.Singleton -@Retention(AnnotationRetention.RUNTIME) -@Qualifier -annotation class ApplicationScope - +/** + * [CoroutineScope] for the application consisting of [SupervisorJob] + * and [DefaultDispatcher] context. + */ @Module @InstallIn(SingletonComponent::class) object CoroutineScopeModule { @@ -22,6 +21,6 @@ object CoroutineScopeModule { @Singleton @ApplicationScope fun provideCoroutineScope( - @DispatcherDefault dispatcherDefault: CoroutineDispatcher - ): CoroutineScope = CoroutineScope(SupervisorJob() + dispatcherDefault) -} \ No newline at end of file + @DefaultDispatcher defaultDispatcher: CoroutineDispatcher, + ): CoroutineScope = CoroutineScope(SupervisorJob() + defaultDispatcher) +} diff --git a/app/src/main/java/me/ash/reader/data/module/DatabaseModule.kt b/app/src/main/java/me/ash/reader/data/module/DatabaseModule.kt index 7f215b9..28aab8f 100644 --- a/app/src/main/java/me/ash/reader/data/module/DatabaseModule.kt +++ b/app/src/main/java/me/ash/reader/data/module/DatabaseModule.kt @@ -13,32 +13,40 @@ import me.ash.reader.data.dao.GroupDao import me.ash.reader.data.source.RYDatabase import javax.inject.Singleton +/** + * Provides Data Access Objects for database. + * + * - [ArticleDao] + * - [FeedDao] + * - [GroupDao] + * - [AccountDao] + */ @Module @InstallIn(SingletonComponent::class) object DatabaseModule { @Provides @Singleton - fun provideArticleDao(RYDatabase: RYDatabase): ArticleDao = - RYDatabase.articleDao() + fun provideArticleDao(ryDatabase: RYDatabase): ArticleDao = + ryDatabase.articleDao() @Provides @Singleton - fun provideFeedDao(RYDatabase: RYDatabase): FeedDao = - RYDatabase.feedDao() + fun provideFeedDao(ryDatabase: RYDatabase): FeedDao = + ryDatabase.feedDao() @Provides @Singleton - fun provideGroupDao(RYDatabase: RYDatabase): GroupDao = - RYDatabase.groupDao() + fun provideGroupDao(ryDatabase: RYDatabase): GroupDao = + ryDatabase.groupDao() @Provides @Singleton - fun provideAccountDao(RYDatabase: RYDatabase): AccountDao = - RYDatabase.accountDao() + fun provideAccountDao(ryDatabase: RYDatabase): AccountDao = + ryDatabase.accountDao() @Provides @Singleton fun provideReaderDatabase(@ApplicationContext context: Context): RYDatabase = RYDatabase.getInstance(context) -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/module/ImageLoaderModule.kt b/app/src/main/java/me/ash/reader/data/module/ImageLoaderModule.kt index 795c2f1..72e7588 100644 --- a/app/src/main/java/me/ash/reader/data/module/ImageLoaderModule.kt +++ b/app/src/main/java/me/ash/reader/data/module/ImageLoaderModule.kt @@ -18,6 +18,9 @@ import kotlinx.coroutines.Dispatchers import okhttp3.OkHttpClient import javax.inject.Singleton +/** + * Provides singleton [ImageLoader] for Coil. + */ @Module @InstallIn(SingletonComponent::class) object ImageLoaderModule { @@ -29,10 +32,14 @@ object ImageLoaderModule { okHttpClient: OkHttpClient, ): ImageLoader { return ImageLoader.Builder(context) + // Shared OKHttpClient instance. .okHttpClient(okHttpClient) - .dispatcher(Dispatchers.Default) // This slightly improves scrolling performance - .components{ + // This slightly improves scrolling performance + .dispatcher(Dispatchers.Default) + .components { + // Support SVG decoding add(SvgDecoder.Factory()) + // Support GIF decoding add( if (SDK_INT >= Build.VERSION_CODES.P) { ImageDecoderDecoder.Factory() @@ -41,12 +48,14 @@ object ImageLoaderModule { } ) } + // Enable disk cache .diskCache( DiskCache.Builder() .directory(context.cacheDir.resolve("images")) .maxSizePercent(0.02) .build() ) + // Enable memory cache .memoryCache( MemoryCache.Builder(context) .maxSizePercent(0.25) diff --git a/app/src/main/java/me/ash/reader/data/module/OkHttpClientModule.kt b/app/src/main/java/me/ash/reader/data/module/OkHttpClientModule.kt index 08e9c32..da1b28c 100644 --- a/app/src/main/java/me/ash/reader/data/module/OkHttpClientModule.kt +++ b/app/src/main/java/me/ash/reader/data/module/OkHttpClientModule.kt @@ -42,13 +42,17 @@ import javax.net.ssl.SSLContext import javax.net.ssl.TrustManager import javax.net.ssl.X509TrustManager +/** + * Provides singleton [OkHttpClient] for the application. + */ @Module @InstallIn(SingletonComponent::class) object OkHttpClientModule { + @Provides @Singleton fun provideOkHttpClient( - @ApplicationContext context: Context + @ApplicationContext context: Context, ): OkHttpClient = cachingHttpClient( cacheDirectory = context.cacheDir.resolve("http") ).newBuilder() @@ -61,7 +65,7 @@ fun cachingHttpClient( cacheSize: Long = 10L * 1024L * 1024L, trustAllCerts: Boolean = true, connectTimeoutSecs: Long = 30L, - readTimeoutSecs: Long = 30L + readTimeoutSecs: Long = 30L, ): OkHttpClient { val builder: OkHttpClient.Builder = OkHttpClient.Builder() @@ -107,6 +111,7 @@ fun OkHttpClient.Builder.trustAllCerts() { } object UserAgentInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { return chain.proceed( chain.request() @@ -117,4 +122,4 @@ object UserAgentInterceptor : Interceptor { } } -const val USER_AGENT_STRING = "ReadYou / ${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE})" \ No newline at end of file +const val USER_AGENT_STRING = "ReadYou / ${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE})" 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 62ac758..0dcea17 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,11 +4,18 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import me.ash.reader.data.source.RYNetworkDataSource import me.ash.reader.data.source.FeverApiDataSource import me.ash.reader.data.source.GoogleReaderApiDataSource +import me.ash.reader.data.source.RYNetworkDataSource import javax.inject.Singleton +/** + * Provides network requests for Retrofit. + * + * - [RYNetworkDataSource]: For network requests within the application + * - [FeverApiDataSource]: For network requests to the Fever API + * - [GoogleReaderApiDataSource]: For network requests to the Google Reader API + */ @Module @InstallIn(SingletonComponent::class) object RetrofitModule { @@ -27,4 +34,4 @@ object RetrofitModule { @Singleton fun provideGoogleReaderApiDataSource(): GoogleReaderApiDataSource = GoogleReaderApiDataSource.getInstance() -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/module/WorkerModule.kt b/app/src/main/java/me/ash/reader/data/module/WorkerModule.kt index 9fcc37c..227518c 100644 --- a/app/src/main/java/me/ash/reader/data/module/WorkerModule.kt +++ b/app/src/main/java/me/ash/reader/data/module/WorkerModule.kt @@ -9,6 +9,9 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton +/** + * Provides singleton [WorkManager] for the application. + */ @Module @InstallIn(SingletonComponent::class) object WorkerModule { @@ -17,4 +20,4 @@ object WorkerModule { @Singleton fun provideWorkManager(@ApplicationContext context: Context): WorkManager = WorkManager.getInstance(context) -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt b/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt index 7f0dddb..47c6bf6 100644 --- a/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt @@ -15,7 +15,11 @@ import me.ash.reader.data.dao.AccountDao import me.ash.reader.data.dao.ArticleDao import me.ash.reader.data.dao.FeedDao import me.ash.reader.data.dao.GroupDao -import me.ash.reader.data.entity.* +import me.ash.reader.data.model.article.Article +import me.ash.reader.data.model.article.ArticleWithFeed +import me.ash.reader.data.model.feed.Feed +import me.ash.reader.data.model.group.Group +import me.ash.reader.data.model.group.GroupWithFeed import me.ash.reader.ui.ext.currentAccountId import java.util.* @@ -29,6 +33,7 @@ abstract class AbstractRssRepository constructor( private val dispatcherIO: CoroutineDispatcher, private val dispatcherDefault: CoroutineDispatcher, ) { + abstract suspend fun updateArticleInfo(article: Article) abstract suspend fun subscribe(feed: Feed, articles: List
) @@ -53,19 +58,17 @@ abstract class AbstractRssRepository constructor( ) } - fun pullGroups(): Flow> { - return groupDao.queryAllGroup(context.currentAccountId).flowOn(dispatcherIO) - } + fun pullGroups(): Flow> = + groupDao.queryAllGroup(context.currentAccountId).flowOn(dispatcherIO) - fun pullFeeds(): Flow> { - return groupDao.queryAllGroupWithFeedAsFlow(context.currentAccountId).flowOn(dispatcherIO) - } + fun pullFeeds(): Flow> = + groupDao.queryAllGroupWithFeedAsFlow(context.currentAccountId).flowOn(dispatcherIO) fun pullArticles( - groupId: String? = null, - feedId: String? = null, - isStarred: Boolean = false, - isUnread: Boolean = false, + groupId: String?, + feedId: String?, + isStarred: Boolean, + isUnread: Boolean, ): PagingSource { val accountId = context.currentAccountId Log.i( @@ -74,32 +77,28 @@ abstract class AbstractRssRepository constructor( ) return when { groupId != null -> when { - isStarred -> articleDao - .queryArticleWithFeedByGroupIdWhenIsStarred(accountId, groupId, isStarred) - isUnread -> articleDao - .queryArticleWithFeedByGroupIdWhenIsUnread(accountId, groupId, isUnread) + isStarred -> articleDao.queryArticleWithFeedByGroupIdWhenIsStarred(accountId, groupId, true) + isUnread -> articleDao.queryArticleWithFeedByGroupIdWhenIsUnread(accountId, groupId, true) else -> articleDao.queryArticleWithFeedByGroupIdWhenIsAll(accountId, groupId) } + feedId != null -> when { - isStarred -> articleDao - .queryArticleWithFeedByFeedIdWhenIsStarred(accountId, feedId, isStarred) - isUnread -> articleDao - .queryArticleWithFeedByFeedIdWhenIsUnread(accountId, feedId, isUnread) + isStarred -> articleDao.queryArticleWithFeedByFeedIdWhenIsStarred(accountId, feedId, true) + isUnread -> articleDao.queryArticleWithFeedByFeedIdWhenIsUnread(accountId, feedId, true) else -> articleDao.queryArticleWithFeedByFeedIdWhenIsAll(accountId, feedId) } + else -> when { - isStarred -> articleDao - .queryArticleWithFeedWhenIsStarred(accountId, isStarred) - isUnread -> articleDao - .queryArticleWithFeedWhenIsUnread(accountId, isUnread) + isStarred -> articleDao.queryArticleWithFeedWhenIsStarred(accountId, true) + isUnread -> articleDao.queryArticleWithFeedWhenIsUnread(accountId, true) else -> articleDao.queryArticleWithFeedWhenIsAll(accountId) } } } fun pullImportant( - isStarred: Boolean = false, - isUnread: Boolean = false, + isStarred: Boolean, + isUnread: Boolean, ): Flow> { val accountId = context.currentAccountId Log.i( @@ -107,42 +106,28 @@ abstract class AbstractRssRepository constructor( "pullImportant: accountId: ${accountId}, isStarred: ${isStarred}, isUnread: ${isUnread}" ) return when { - isStarred -> articleDao - .queryImportantCountWhenIsStarred(accountId, isStarred) - isUnread -> articleDao - .queryImportantCountWhenIsUnread(accountId, isUnread) + isStarred -> articleDao.queryImportantCountWhenIsStarred(accountId, true) + isUnread -> articleDao.queryImportantCountWhenIsUnread(accountId, true) else -> articleDao.queryImportantCountWhenIsAll(accountId) }.mapLatest { mapOf( // Groups - *(it.groupBy { it.groupId }.map { - it.key to it.value.sumOf { it.important } - }.toTypedArray()), + *(it.groupBy { it.groupId }.map { it.key to it.value.sumOf { it.important } }.toTypedArray()), // Feeds - *(it.map { - it.feedId to it.important - }.toTypedArray()), + *(it.map { it.feedId to it.important }.toTypedArray()), // All summary "sum" to it.sumOf { it.important } ) }.flowOn(dispatcherDefault) } - suspend fun findFeedById(id: String): Feed? { - return feedDao.queryById(id) - } + suspend fun findFeedById(id: String): Feed? = feedDao.queryById(id) - suspend fun findGroupById(id: String): Group? { - return groupDao.queryById(id) - } + suspend fun findGroupById(id: String): Group? = groupDao.queryById(id) - suspend fun findArticleById(id: String): ArticleWithFeed? { - return articleDao.queryById(id) - } + suspend fun findArticleById(id: String): ArticleWithFeed? = articleDao.queryById(id) - suspend fun isFeedExist(url: String): Boolean { - return feedDao.queryByLink(context.currentAccountId, url).isNotEmpty() - } + suspend fun isFeedExist(url: String): Boolean = feedDao.queryByLink(context.currentAccountId, url).isNotEmpty() suspend fun updateGroup(group: Group) { groupDao.update(group) @@ -184,10 +169,10 @@ abstract class AbstractRssRepository constructor( fun searchArticles( content: String, - groupId: String? = null, - feedId: String? = null, - isStarred: Boolean = false, - isUnread: Boolean = false, + groupId: String?, + feedId: String?, + isStarred: Boolean, + isUnread: Boolean, ): PagingSource { val accountId = context.currentAccountId Log.i( @@ -196,22 +181,20 @@ abstract class AbstractRssRepository constructor( ) return when { groupId != null -> when { - isStarred -> articleDao - .searchArticleByGroupIdWhenIsStarred(accountId, content, groupId, isStarred) - isUnread -> articleDao - .searchArticleByGroupIdWhenIsUnread(accountId, content, groupId, isUnread) + isStarred -> articleDao.searchArticleByGroupIdWhenIsStarred(accountId, content, groupId, true) + isUnread -> articleDao.searchArticleByGroupIdWhenIsUnread(accountId, content, groupId, true) else -> articleDao.searchArticleByGroupIdWhenAll(accountId, content, groupId) } + feedId != null -> when { - isStarred -> articleDao - .searchArticleByFeedIdWhenIsStarred(accountId, content, feedId, isStarred) - isUnread -> articleDao - .searchArticleByFeedIdWhenIsUnread(accountId, content, feedId, isUnread) + isStarred -> articleDao.searchArticleByFeedIdWhenIsStarred(accountId, content, feedId, true) + isUnread -> articleDao.searchArticleByFeedIdWhenIsUnread(accountId, content, feedId, true) else -> articleDao.searchArticleByFeedIdWhenAll(accountId, content, feedId) } + else -> when { - isStarred -> articleDao.searchArticleWhenIsStarred(accountId, content, isStarred) - isUnread -> articleDao.searchArticleWhenIsUnread(accountId, content, isUnread) + isStarred -> articleDao.searchArticleWhenIsStarred(accountId, content, true) + isUnread -> articleDao.searchArticleWhenIsUnread(accountId, content, true) else -> articleDao.searchArticleWhenAll(accountId, content) } } diff --git a/app/src/main/java/me/ash/reader/data/repository/AccountRepository.kt b/app/src/main/java/me/ash/reader/data/repository/AccountRepository.kt index 43c45ce..e25b354 100644 --- a/app/src/main/java/me/ash/reader/data/repository/AccountRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/AccountRepository.kt @@ -5,8 +5,9 @@ import dagger.hilt.android.qualifiers.ApplicationContext import me.ash.reader.R import me.ash.reader.data.dao.AccountDao import me.ash.reader.data.dao.GroupDao -import me.ash.reader.data.entity.Account -import me.ash.reader.data.entity.Group +import me.ash.reader.data.model.account.Account +import me.ash.reader.data.model.account.AccountType +import me.ash.reader.data.model.group.Group import me.ash.reader.ui.ext.currentAccountId import me.ash.reader.ui.ext.getDefaultGroupId import javax.inject.Inject @@ -18,20 +19,16 @@ class AccountRepository @Inject constructor( private val groupDao: GroupDao, ) { - suspend fun getCurrentAccount(): Account? { - return accountDao.queryById(context.currentAccountId) - } + suspend fun getCurrentAccount(): Account? = accountDao.queryById(context.currentAccountId) - suspend fun isNoAccount(): Boolean { - return accountDao.queryAll().isEmpty() - } + suspend fun isNoAccount(): Boolean = accountDao.queryAll().isEmpty() suspend fun addDefaultAccount(): Account { val readYouString = context.getString(R.string.read_you) val defaultString = context.getString(R.string.defaults) return Account( name = readYouString, - type = Account.Type.LOCAL, + type = AccountType.Local, ).apply { id = accountDao.insert(this).toInt() }.also { @@ -46,4 +43,4 @@ class AccountRepository @Inject constructor( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt b/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt index 819700f..93a2d69 100644 --- a/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt @@ -14,12 +14,12 @@ import me.ash.reader.data.dao.AccountDao import me.ash.reader.data.dao.ArticleDao import me.ash.reader.data.dao.FeedDao import me.ash.reader.data.dao.GroupDao -import me.ash.reader.data.entity.Article -import me.ash.reader.data.entity.Feed -import me.ash.reader.data.entity.FeedWithArticle -import me.ash.reader.data.entity.Group -import me.ash.reader.data.module.DispatcherDefault -import me.ash.reader.data.module.DispatcherIO +import me.ash.reader.data.model.article.Article +import me.ash.reader.data.model.feed.Feed +import me.ash.reader.data.model.feed.FeedWithArticle +import me.ash.reader.data.model.group.Group +import me.ash.reader.data.module.DefaultDispatcher +import me.ash.reader.data.module.IODispatcher import me.ash.reader.data.repository.SyncWorker.Companion.setIsSyncing import me.ash.reader.ui.ext.currentAccountId import me.ash.reader.ui.ext.spacerDollar @@ -35,14 +35,14 @@ class LocalRssRepository @Inject constructor( private val notificationHelper: NotificationHelper, private val accountDao: AccountDao, private val groupDao: GroupDao, - @DispatcherIO - private val dispatcherIO: CoroutineDispatcher, - @DispatcherDefault - private val dispatcherDefault: CoroutineDispatcher, + @IODispatcher + private val ioDispatcher: CoroutineDispatcher, + @DefaultDispatcher + private val defaultDispatcher: CoroutineDispatcher, workManager: WorkManager, ) : AbstractRssRepository( context, accountDao, articleDao, groupDao, - feedDao, workManager, dispatcherIO, dispatcherDefault + feedDao, workManager, ioDispatcher, defaultDispatcher ) { override suspend fun updateArticleInfo(article: Article) { @@ -70,8 +70,8 @@ class LocalRssRepository @Inject constructor( } } - override suspend fun sync(coroutineWorker: CoroutineWorker): ListenableWorker.Result { - return supervisorScope { + override suspend fun sync(coroutineWorker: CoroutineWorker): ListenableWorker.Result = + supervisorScope { val preTime = System.currentTimeMillis() val accountId = context.currentAccountId feedDao.queryAll(accountId) @@ -81,31 +81,23 @@ class LocalRssRepository @Inject constructor( it.map { feed -> async { syncFeed(feed) } } .awaitAll() .forEach { - if (it.isNotify) { - notificationHelper.notify( - FeedWithArticle( - it.feedWithArticle.feed, - articleDao.insertListIfNotExist(it.feedWithArticle.articles) - ) - ) + if (it.feed.isNotification) { + notificationHelper.notify(it.apply { + articles = articleDao.insertListIfNotExist(it.articles) + }) } else { - articleDao.insertListIfNotExist(it.feedWithArticle.articles) + articleDao.insertListIfNotExist(it.articles) } } } Log.i("RlOG", "onCompletion: ${System.currentTimeMillis() - preTime}") accountDao.queryById(accountId)?.let { account -> - accountDao.update( - account.apply { - updateAt = Date() - } - ) + accountDao.update(account.apply { updateAt = Date() }) } coroutineWorker.setProgress(setIsSyncing(false)) ListenableWorker.Result.success() } - } override suspend fun markAsRead( groupId: String?, @@ -124,6 +116,7 @@ class LocalRssRepository @Inject constructor( before = before ?: Date(Long.MAX_VALUE) ) } + feedId != null -> { articleDao.markAllAsReadByFeedId( accountId = accountId, @@ -132,41 +125,34 @@ class LocalRssRepository @Inject constructor( before = before ?: Date(Long.MAX_VALUE) ) } + articleId != null -> { articleDao.markAsReadByArticleId(accountId, articleId, isUnread) } + else -> { articleDao.markAllAsRead(accountId, isUnread, before ?: Date(Long.MAX_VALUE)) } } } - data class ArticleNotify( - val feedWithArticle: FeedWithArticle, - val isNotify: Boolean, - ) - - private suspend fun syncFeed(feed: Feed): ArticleNotify { + private suspend fun syncFeed(feed: Feed): FeedWithArticle { val latest = articleDao.queryLatestByFeedId(context.currentAccountId, feed.id) - val articles: List
? - try { - articles = rssHelper.queryRssXml(feed, latest?.link) - } catch (e: Exception) { - e.printStackTrace() - Log.e("RLog", "queryRssXml[${feed.name}]: ${e.message}") - return ArticleNotify(FeedWithArticle(feed, listOf()), false) - } - try { + val articles = rssHelper.queryRssXml(feed, latest?.link) +// try { // if (feed.icon == null && !articles.isNullOrEmpty()) { // rssHelper.queryRssIcon(feedDao, feed, articles.first().link) // } - } catch (e: Exception) { - Log.e("RLog", "queryRssIcon[${feed.name}]: ${e.message}") - return ArticleNotify(FeedWithArticle(feed, listOf()), false) - } - return ArticleNotify( - feedWithArticle = FeedWithArticle(feed, articles), - isNotify = articles.isNotEmpty() && feed.isNotification +// } catch (e: Exception) { +// Log.e("RLog", "queryRssIcon[${feed.name}]: ${e.message}") +// return FeedWithArticle( +// feed = feed.apply { isNotification = false }, +// articles = listOf() +// ) +// } + return FeedWithArticle( + feed = feed.apply { isNotification = feed.isNotification && articles.isNotEmpty() }, + articles = articles ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/repository/NotificationHelper.kt b/app/src/main/java/me/ash/reader/data/repository/NotificationHelper.kt index ee421c8..3084009 100644 --- a/app/src/main/java/me/ash/reader/data/repository/NotificationHelper.kt +++ b/app/src/main/java/me/ash/reader/data/repository/NotificationHelper.kt @@ -9,7 +9,7 @@ import androidx.core.app.NotificationManagerCompat import dagger.hilt.android.qualifiers.ApplicationContext import me.ash.reader.MainActivity import me.ash.reader.R -import me.ash.reader.data.entity.FeedWithArticle +import me.ash.reader.data.model.feed.FeedWithArticle import me.ash.reader.ui.page.common.ExtraName import me.ash.reader.ui.page.common.NotificationGroupName import java.util.* @@ -19,6 +19,7 @@ class NotificationHelper @Inject constructor( @ApplicationContext private val context: Context, ) { + private val notificationManager: NotificationManagerCompat = NotificationManagerCompat.from(context).apply { createNotificationChannel( @@ -30,9 +31,7 @@ class NotificationHelper @Inject constructor( ) } - fun notify( - feedWithArticle: FeedWithArticle, - ) { + fun notify(feedWithArticle: FeedWithArticle) { notificationManager.createNotificationChannelGroup( NotificationChannelGroup( feedWithArticle.feed.id, diff --git a/app/src/main/java/me/ash/reader/data/repository/OpmlRepository.kt b/app/src/main/java/me/ash/reader/data/repository/OpmlRepository.kt index 774be76..f397f1d 100644 --- a/app/src/main/java/me/ash/reader/data/repository/OpmlRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/OpmlRepository.kt @@ -10,7 +10,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import me.ash.reader.data.dao.AccountDao import me.ash.reader.data.dao.FeedDao import me.ash.reader.data.dao.GroupDao -import me.ash.reader.data.entity.Feed +import me.ash.reader.data.model.feed.Feed import me.ash.reader.data.source.OpmlLocalDataSource import me.ash.reader.ui.ext.currentAccountId import me.ash.reader.ui.ext.getDefaultGroupId @@ -18,6 +18,9 @@ import java.io.InputStream import java.util.* import javax.inject.Inject +/** + * Supports import and export from OPML files. + */ class OpmlRepository @Inject constructor( @ApplicationContext private val context: Context, @@ -27,6 +30,12 @@ class OpmlRepository @Inject constructor( private val rssRepository: RssRepository, private val opmlLocalDataSource: OpmlLocalDataSource, ) { + + /** + * Imports OPML file. + * + * @param [inputStream] input stream of OPML file + */ @Throws(Exception::class) suspend fun saveToDatabase(inputStream: InputStream) { val defaultGroup = groupDao.queryById(getDefaultGroupId())!! @@ -47,6 +56,9 @@ class OpmlRepository @Inject constructor( } } + /** + * Exports OPML file. + */ @Throws(Exception::class) suspend fun saveToString(): String { val defaultGroup = groupDao.queryById(getDefaultGroupId())!! @@ -85,7 +97,5 @@ class OpmlRepository @Inject constructor( )!! } - private fun getDefaultGroupId(): String { - return context.currentAccountId.getDefaultGroupId() - } -} \ No newline at end of file + private fun getDefaultGroupId(): String = context.currentAccountId.getDefaultGroupId() +} diff --git a/app/src/main/java/me/ash/reader/data/repository/RYRepository.kt b/app/src/main/java/me/ash/reader/data/repository/RYRepository.kt index b9ce8ac..c65ef98 100644 --- a/app/src/main/java/me/ash/reader/data/repository/RYRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/RYRepository.kt @@ -8,11 +8,11 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.withContext import me.ash.reader.R -import me.ash.reader.data.model.toVersion -import me.ash.reader.data.module.DispatcherIO -import me.ash.reader.data.module.DispatcherMain -import me.ash.reader.data.preference.* -import me.ash.reader.data.preference.NewVersionSizePreference.formatSize +import me.ash.reader.data.model.general.toVersion +import me.ash.reader.data.model.preference.* +import me.ash.reader.data.model.preference.NewVersionSizePreference.formatSize +import me.ash.reader.data.module.IODispatcher +import me.ash.reader.data.module.MainDispatcher import me.ash.reader.data.source.Download import me.ash.reader.data.source.RYNetworkDataSource import me.ash.reader.data.source.downloadToFileWithProgress @@ -26,34 +26,35 @@ class RYRepository @Inject constructor( @ApplicationContext private val context: Context, private val RYNetworkDataSource: RYNetworkDataSource, - @DispatcherIO - private val dispatcherIO: CoroutineDispatcher, - @DispatcherMain - private val dispatcherMain: CoroutineDispatcher, + @IODispatcher + private val ioDispatcher: CoroutineDispatcher, + @MainDispatcher + private val mainDispatcher: CoroutineDispatcher, ) { - suspend fun checkUpdate(showToast: Boolean = true): Boolean? = withContext(dispatcherIO) { + + suspend fun checkUpdate(showToast: Boolean = true): Boolean? = withContext(ioDispatcher) { try { - val response = - RYNetworkDataSource.getReleaseLatest(context.getString(R.string.update_link)) + val response = RYNetworkDataSource.getReleaseLatest(context.getString(R.string.update_link)) when { response.code() == 403 -> { - withContext(dispatcherMain) { + withContext(mainDispatcher) { if (showToast) context.showToast(context.getString(R.string.rate_limit)) } return@withContext null } + response.body() == null -> { - withContext(dispatcherMain) { + withContext(mainDispatcher) { if (showToast) context.showToast(context.getString(R.string.check_failure)) } return@withContext null } } + val skipVersion = context.skipVersionNumber.toVersion() + val currentVersion = context.getCurrentVersion() val latest = response.body()!! val latestVersion = latest.tag_name.toVersion() // val latestVersion = "1.0.0".toVersion() - val skipVersion = context.skipVersionNumber.toVersion() - val currentVersion = context.getCurrentVersion() val latestLog = latest.body ?: "" val latestPublishDate = latest.published_at ?: latest.created_at ?: "" val latestSize = latest.assets?.first()?.size ?: 0 @@ -74,7 +75,7 @@ class RYRepository @Inject constructor( } catch (e: Exception) { e.printStackTrace() Log.e("RLog", "checkUpdate: ${e.message}") - withContext(dispatcherMain) { + withContext(mainDispatcher) { if (showToast) context.showToast(context.getString(R.string.check_failure)) } null @@ -82,7 +83,7 @@ class RYRepository @Inject constructor( } suspend fun downloadFile(url: String): Flow = - withContext(dispatcherIO) { + withContext(ioDispatcher) { Log.i("RLog", "downloadFile start: $url") try { return@withContext RYNetworkDataSource.downloadFile(url) @@ -90,10 +91,10 @@ class RYRepository @Inject constructor( } catch (e: Exception) { e.printStackTrace() Log.e("RLog", "downloadFile: ${e.message}") - withContext(dispatcherMain) { + withContext(mainDispatcher) { context.showToast(context.getString(R.string.download_failure)) } } emptyFlow() } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/repository/RssHelper.kt b/app/src/main/java/me/ash/reader/data/repository/RssHelper.kt index 78b3911..b95cf47 100644 --- a/app/src/main/java/me/ash/reader/data/repository/RssHelper.kt +++ b/app/src/main/java/me/ash/reader/data/repository/RssHelper.kt @@ -10,13 +10,12 @@ import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext import me.ash.reader.data.dao.FeedDao -import me.ash.reader.data.entity.Article -import me.ash.reader.data.entity.Feed -import me.ash.reader.data.entity.FeedWithArticle -import me.ash.reader.data.module.DispatcherIO +import me.ash.reader.data.model.article.Article +import me.ash.reader.data.model.feed.Feed +import me.ash.reader.data.model.feed.FeedWithArticle +import me.ash.reader.data.module.IODispatcher import me.ash.reader.ui.ext.currentAccountId import me.ash.reader.ui.ext.spacerDollar -import net.dankito.readability4j.Readability4J import net.dankito.readability4j.extended.Readability4JExtended import okhttp3.OkHttpClient import okhttp3.Request @@ -25,16 +24,20 @@ import java.io.InputStream import java.util.* import javax.inject.Inject +/** + * Some operations on RSS. + */ class RssHelper @Inject constructor( @ApplicationContext private val context: Context, - @DispatcherIO - private val dispatcherIO: CoroutineDispatcher, + @IODispatcher + private val ioDispatcher: CoroutineDispatcher, private val okHttpClient: OkHttpClient, ) { + @Throws(Exception::class) suspend fun searchFeed(feedLink: String): FeedWithArticle { - return withContext(dispatcherIO) { + return withContext(ioDispatcher) { val accountId = context.currentAccountId val syndFeed = SyndFeedInput().build(XmlReader(inputStream(okHttpClient, feedLink))) val feed = Feed( @@ -49,16 +52,9 @@ class RssHelper @Inject constructor( } } - fun parseDescriptionContent(link: String, content: String): String { - val readability4J: Readability4J = Readability4JExtended(link, content) - val article = readability4J.parse() - val element = article.articleContent - return element.toString() - } - @Throws(Exception::class) suspend fun parseFullContent(link: String, title: String): String { - return withContext(dispatcherIO) { + return withContext(ioDispatcher) { val response = response(okHttpClient, link) val content = response.body.string() val readability4J = Readability4JExtended(link, content) @@ -75,27 +71,31 @@ class RssHelper @Inject constructor( } } - @Throws(Exception::class) suspend fun queryRssXml( feed: Feed, - latestLink: String? = null, - ): List
{ - val accountId = context.currentAccountId - return inputStream(okHttpClient, feed.url).use { - SyndFeedInput().apply { isPreserveWireFeed = true } - .build(XmlReader(it)) - .entries - .asSequence() - .takeWhile { latestLink == null || latestLink != it.link } - .map { article(feed, accountId, it) } - .toList() + latestLink: String?, + ): List
= + try { + val accountId = context.currentAccountId + inputStream(okHttpClient, feed.url).use { + SyndFeedInput().apply { isPreserveWireFeed = true } + .build(XmlReader(it)) + .entries + .asSequence() + .takeWhile { latestLink == null || latestLink != it.link } + .map { article(feed, accountId, it) } + .toList() + } + } catch (e: Exception) { + e.printStackTrace() + Log.e("RLog", "queryRssXml[${feed.name}]: ${e.message}") + listOf() } - } private fun article( feed: Feed, accountId: Int, - syndEntry: SyndEntry + syndEntry: SyndEntry, ): Article { val desc = syndEntry.description?.value val content = syndEntry.contents @@ -144,7 +144,7 @@ class RssHelper @Inject constructor( feed: Feed, articleLink: String, ) { - withContext(dispatcherIO) { + withContext(ioDispatcher) { val domainRegex = Regex("(http|https)://(www.)?(\\w+(\\.)?)+") val request = response(okHttpClient, articleLink) val content = request.body.string() @@ -183,11 +183,11 @@ class RssHelper @Inject constructor( private suspend fun inputStream( client: OkHttpClient, - url: String + url: String, ): InputStream = response(client, url).body.byteStream() private suspend fun response( client: OkHttpClient, - url: String + url: String, ) = client.newCall(Request.Builder().url(url).build()).executeAsync() } diff --git a/app/src/main/java/me/ash/reader/data/repository/RssRepository.kt b/app/src/main/java/me/ash/reader/data/repository/RssRepository.kt index ab0dd35..8547dd6 100644 --- a/app/src/main/java/me/ash/reader/data/repository/RssRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/RssRepository.kt @@ -2,7 +2,7 @@ package me.ash.reader.data.repository import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext -import me.ash.reader.data.entity.Account +import me.ash.reader.data.model.account.AccountType import me.ash.reader.ui.ext.currentAccountType import javax.inject.Inject @@ -13,8 +13,9 @@ class RssRepository @Inject constructor( // private val feverRssRepository: FeverRssRepository, // private val googleReaderRssRepository: GoogleReaderRssRepository, ) { + fun get() = when (context.currentAccountType) { - Account.Type.LOCAL -> localRssRepository + AccountType.Local.id -> localRssRepository // Account.Type.LOCAL -> feverRssRepository // Account.Type.FEVER -> feverRssRepository // Account.Type.GOOGLE_READER -> googleReaderRssRepository diff --git a/app/src/main/java/me/ash/reader/data/repository/StringsRepository.kt b/app/src/main/java/me/ash/reader/data/repository/StringsRepository.kt index 5497c77..771b1be 100644 --- a/app/src/main/java/me/ash/reader/data/repository/StringsRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/StringsRepository.kt @@ -10,6 +10,7 @@ class StringsRepository @Inject constructor( @ApplicationContext private val context: Context, ) { + fun getString(resId: Int, vararg formatArgs: Any) = context.getString(resId, *formatArgs) fun getQuantityString(resId: Int, quantity: Int, vararg formatArgs: Any) = @@ -18,6 +19,6 @@ class StringsRepository @Inject constructor( fun formatAsString( date: Date?, onlyHourMinute: Boolean? = false, - atHourMinute: Boolean? = false + atHourMinute: Boolean? = false, ) = date?.formatAsString(context, onlyHourMinute, atHourMinute) } diff --git a/app/src/main/java/me/ash/reader/data/repository/SyncWorker.kt b/app/src/main/java/me/ash/reader/data/repository/SyncWorker.kt index 36b419d..594e208 100644 --- a/app/src/main/java/me/ash/reader/data/repository/SyncWorker.kt +++ b/app/src/main/java/me/ash/reader/data/repository/SyncWorker.kt @@ -18,17 +18,17 @@ class SyncWorker @AssistedInject constructor( private val rssRepository: RssRepository, ) : CoroutineWorker(context, workerParams) { - override suspend fun doWork(): Result { - Log.i("RLog", "doWork: ") - return withContext(Dispatchers.Default) { + override suspend fun doWork(): Result = + withContext(Dispatchers.Default) { + Log.i("RLog", "doWork: ") rssRepository.get().sync(this@SyncWorker) } - } companion object { + const val WORK_NAME = "article.sync" - val UUID: UUID + val uuid: UUID val repeatingRequest = PeriodicWorkRequestBuilder( 15, TimeUnit.MINUTES @@ -36,10 +36,10 @@ class SyncWorker @AssistedInject constructor( Constraints.Builder() .build() ).addTag(WORK_NAME).build().also { - UUID = it.id + uuid = it.id } fun setIsSyncing(boolean: Boolean) = workDataOf("isSyncing" to boolean) fun Data.getIsSyncing(): Boolean = getBoolean("isSyncing", false) } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/source/FeverApiDataSource.kt b/app/src/main/java/me/ash/reader/data/source/FeverApiDataSource.kt index 616ae7c..7676f3d 100644 --- a/app/src/main/java/me/ash/reader/data/source/FeverApiDataSource.kt +++ b/app/src/main/java/me/ash/reader/data/source/FeverApiDataSource.kt @@ -11,6 +11,7 @@ import retrofit2.http.Part import retrofit2.http.Query interface FeverApiDataSource { + @Multipart @POST("fever.php/?api=&feeds=") fun feeds(@Part("api_key") apiKey: RequestBody? = "1352b707f828a6f502db3768fa8d7151".toRequestBody()): Call @@ -23,7 +24,7 @@ interface FeverApiDataSource { @POST("fever.php/?api=&items=") fun itemsBySince( @Query("since_id") since: Long, - @Part("api_key") apiKey: RequestBody? = "1352b707f828a6f502db3768fa8d7151".toRequestBody() + @Part("api_key") apiKey: RequestBody? = "1352b707f828a6f502db3768fa8d7151".toRequestBody(), ): Call @Multipart @@ -38,10 +39,11 @@ interface FeverApiDataSource { @POST("fever.php/?api=&items=") fun itemsByIds( @Query("with_ids") ids: String, - @Part("api_key") apiKey: RequestBody? = "1352b707f828a6f502db3768fa8d7151".toRequestBody() + @Part("api_key") apiKey: RequestBody? = "1352b707f828a6f502db3768fa8d7151".toRequestBody(), ): Call companion object { + private var instance: FeverApiDataSource? = null fun getInstance(): FeverApiDataSource { @@ -55,4 +57,4 @@ interface FeverApiDataSource { } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/source/GoogleReaderApiDataSource.kt b/app/src/main/java/me/ash/reader/data/source/GoogleReaderApiDataSource.kt index 9295322..7684b72 100644 --- a/app/src/main/java/me/ash/reader/data/source/GoogleReaderApiDataSource.kt +++ b/app/src/main/java/me/ash/reader/data/source/GoogleReaderApiDataSource.kt @@ -7,6 +7,7 @@ import retrofit2.http.Headers import retrofit2.http.POST interface GoogleReaderApiDataSource { + @POST("accounts/ClientLogin") fun login(Email: String, Passwd: String): Call @@ -27,6 +28,7 @@ interface GoogleReaderApiDataSource { fun readingList(): Call companion object { + private var instance: GoogleReaderApiDataSource? = null fun getInstance(): GoogleReaderApiDataSource { @@ -40,4 +42,4 @@ interface GoogleReaderApiDataSource { } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/source/OpmlLocalDataSource.kt b/app/src/main/java/me/ash/reader/data/source/OpmlLocalDataSource.kt index 53b3c3b..7d653ea 100644 --- a/app/src/main/java/me/ash/reader/data/source/OpmlLocalDataSource.kt +++ b/app/src/main/java/me/ash/reader/data/source/OpmlLocalDataSource.kt @@ -5,10 +5,10 @@ import be.ceau.opml.OpmlParser import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext -import me.ash.reader.data.entity.Feed -import me.ash.reader.data.entity.Group -import me.ash.reader.data.entity.GroupWithFeed -import me.ash.reader.data.module.DispatcherIO +import me.ash.reader.data.model.feed.Feed +import me.ash.reader.data.model.group.Group +import me.ash.reader.data.model.group.GroupWithFeed +import me.ash.reader.data.module.IODispatcher import me.ash.reader.ui.ext.currentAccountId import me.ash.reader.ui.ext.spacerDollar import java.io.InputStream @@ -18,15 +18,16 @@ import javax.inject.Inject class OpmlLocalDataSource @Inject constructor( @ApplicationContext private val context: Context, - @DispatcherIO - private val dispatcherIO: CoroutineDispatcher, + @IODispatcher + private val ioDispatcher: CoroutineDispatcher, ) { + @Throws(Exception::class) suspend fun parseFileInputStream( inputStream: InputStream, - defaultGroup: Group + defaultGroup: Group, ): List { - return withContext(dispatcherIO) { + return withContext(ioDispatcher) { val accountId = context.currentAccountId val opml = OpmlParser().parse(inputStream) val groupWithFeedList = mutableListOf().also { @@ -109,4 +110,4 @@ class OpmlLocalDataSource @Inject constructor( private fun MutableList.addFeedToDefault(feed: Feed) { first().feeds.add(feed) } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/source/RYDatabase.kt b/app/src/main/java/me/ash/reader/data/source/RYDatabase.kt index e3cf064..19b5463 100644 --- a/app/src/main/java/me/ash/reader/data/source/RYDatabase.kt +++ b/app/src/main/java/me/ash/reader/data/source/RYDatabase.kt @@ -8,24 +8,27 @@ import me.ash.reader.data.dao.AccountDao import me.ash.reader.data.dao.ArticleDao import me.ash.reader.data.dao.FeedDao import me.ash.reader.data.dao.GroupDao -import me.ash.reader.data.entity.Account -import me.ash.reader.data.entity.Article -import me.ash.reader.data.entity.Feed -import me.ash.reader.data.entity.Group +import me.ash.reader.data.model.account.Account +import me.ash.reader.data.model.account.AccountTypeConverters +import me.ash.reader.data.model.article.Article +import me.ash.reader.data.model.feed.Feed +import me.ash.reader.data.model.group.Group import java.util.* @Database( entities = [Account::class, Feed::class, Article::class, Group::class], - version = 2, + version = 2 ) -@TypeConverters(RYDatabase.Converters::class) +@TypeConverters(RYDatabase.DateConverters::class, AccountTypeConverters::class) abstract class RYDatabase : RoomDatabase() { + abstract fun accountDao(): AccountDao abstract fun feedDao(): FeedDao abstract fun articleDao(): ArticleDao abstract fun groupDao(): GroupDao companion object { + private var instance: RYDatabase? = null fun getInstance(context: Context): RYDatabase { @@ -41,7 +44,7 @@ abstract class RYDatabase : RoomDatabase() { } } - class Converters { + class DateConverters { @TypeConverter fun toDate(dateLong: Long?): Date? { @@ -61,6 +64,7 @@ val allMigrations = arrayOf( @Suppress("ClassName") object MIGRATION_1_2 : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( """ @@ -68,4 +72,4 @@ object MIGRATION_1_2 : Migration(1, 2) { """.trimIndent() ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/data/source/RYNetworkDataSource.kt b/app/src/main/java/me/ash/reader/data/source/RYNetworkDataSource.kt index f1e4fdc..45454e2 100644 --- a/app/src/main/java/me/ash/reader/data/source/RYNetworkDataSource.kt +++ b/app/src/main/java/me/ash/reader/data/source/RYNetworkDataSource.kt @@ -15,6 +15,7 @@ import retrofit2.http.Url import java.io.File interface RYNetworkDataSource { + @GET suspend fun getReleaseLatest(@Url url: String): Response @@ -23,6 +24,7 @@ interface RYNetworkDataSource { suspend fun downloadFile(@Url url: String): ResponseBody companion object { + private var instance: RYNetworkDataSource? = null fun getInstance(): RYNetworkDataSource { @@ -69,8 +71,10 @@ fun ResponseBody.downloadToFileWithProgress(saveFile: File): Flow = when { progressBytes < totalBytes -> throw Exception("missing bytes") + progressBytes > totalBytes -> throw Exception("too many bytes") + else -> deleteFile = false } @@ -113,4 +117,4 @@ sealed class Download { object NotYet : Download() data class Progress(val percent: Int) : Download() data class Finished(val file: File) : Download() -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/component/ChangeUrlDialog.kt b/app/src/main/java/me/ash/reader/ui/component/ChangeUrlDialog.kt new file mode 100644 index 0000000..c73aa04 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/component/ChangeUrlDialog.kt @@ -0,0 +1,28 @@ +package me.ash.reader.ui.component + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Edit +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import me.ash.reader.R +import me.ash.reader.ui.component.base.TextFieldDialog + +@Composable +fun ChangeUrlDialog( + visible: Boolean = false, + value: String = "", + onValueChange: (String) -> Unit = {}, + onDismissRequest: () -> Unit = {}, + onConfirm: (String) -> Unit = {}, +) { + TextFieldDialog( + visible = visible, + title = stringResource(R.string.change_url), + icon = Icons.Outlined.Edit, + value = value, + placeholder = stringResource(R.string.feed_url_placeholder), + onValueChange = onValueChange, + onDismissRequest = onDismissRequest, + onConfirm = onConfirm, + ) +} diff --git a/app/src/main/java/me/ash/reader/ui/component/FeedIcon.kt b/app/src/main/java/me/ash/reader/ui/component/FeedIcon.kt index d8b0030..399dfeb 100644 --- a/app/src/main/java/me/ash/reader/ui/component/FeedIcon.kt +++ b/app/src/main/java/me/ash/reader/ui/component/FeedIcon.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.unit.dp @Composable fun FeedIcon( feedName: String, - size: Dp = 20.dp + size: Dp = 20.dp, ) { Row( modifier = Modifier diff --git a/app/src/main/java/me/ash/reader/ui/component/FilterBar.kt b/app/src/main/java/me/ash/reader/ui/component/FilterBar.kt index f0d29d9..b2c8163 100644 --- a/app/src/main/java/me/ash/reader/ui/component/FilterBar.kt +++ b/app/src/main/java/me/ash/reader/ui/component/FilterBar.kt @@ -12,10 +12,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp -import me.ash.reader.data.model.Filter -import me.ash.reader.data.model.getName -import me.ash.reader.data.preference.FlowFilterBarStylePreference -import me.ash.reader.data.preference.LocalThemeIndex +import me.ash.reader.data.model.general.Filter +import me.ash.reader.data.model.preference.FlowFilterBarStylePreference +import me.ash.reader.data.model.preference.LocalThemeIndex import me.ash.reader.ui.ext.surfaceColorAtElevation import me.ash.reader.ui.theme.palette.onDark @@ -54,7 +53,7 @@ fun FilterBar( } else { item.iconOutline }, - contentDescription = item.getName() + contentDescription = item.toName() ) }, label = if (filterBarStyle == FlowFilterBarStylePreference.Icon.value) { @@ -62,7 +61,7 @@ fun FilterBar( } else { { Text( - text = item.getName(), + text = item.toName(), style = MaterialTheme.typography.labelLarge, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -86,4 +85,4 @@ fun FilterBar( } Spacer(modifier = Modifier.width(filterBarPadding)) } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/component/RenameDialog.kt b/app/src/main/java/me/ash/reader/ui/component/RenameDialog.kt new file mode 100644 index 0000000..e40a524 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/component/RenameDialog.kt @@ -0,0 +1,28 @@ +package me.ash.reader.ui.component + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Edit +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import me.ash.reader.R +import me.ash.reader.ui.component.base.TextFieldDialog + +@Composable +fun RenameDialog( + visible: Boolean = false, + value: String = "", + onValueChange: (String) -> Unit = {}, + onDismissRequest: () -> Unit = {}, + onConfirm: (String) -> Unit = {}, +) { + TextFieldDialog( + visible = visible, + title = stringResource(R.string.rename), + icon = Icons.Outlined.Edit, + value = value, + placeholder = stringResource(R.string.name), + onValueChange = onValueChange, + onDismissRequest = onDismissRequest, + onConfirm = onConfirm + ) +} diff --git a/app/src/main/java/me/ash/reader/ui/component/base/AnimatedPopup.kt b/app/src/main/java/me/ash/reader/ui/component/base/AnimatedPopup.kt index 9b873e6..7192863 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/AnimatedPopup.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/AnimatedPopup.kt @@ -29,7 +29,7 @@ fun AnimatedPopup( anchorBounds: IntRect, windowSize: IntSize, layoutDirection: LayoutDirection, - popupContentSize: IntSize + popupContentSize: IntSize, ): IntOffset { return IntOffset( x = with(density) { (absoluteX).roundToPx() }, @@ -42,4 +42,4 @@ fun AnimatedPopup( content() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/component/base/AnimatedText.kt b/app/src/main/java/me/ash/reader/ui/component/base/AnimatedText.kt index 7c7e76c..d4ba8a6 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/AnimatedText.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/AnimatedText.kt @@ -36,7 +36,7 @@ fun AnimatedText( softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current + style: TextStyle = LocalTextStyle.current, ) { AnimatedContent( targetState = text, diff --git a/app/src/main/java/me/ash/reader/ui/component/base/ClipboardTextField.kt b/app/src/main/java/me/ash/reader/ui/component/base/ClipboardTextField.kt index cff26ee..eae0a4f 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/ClipboardTextField.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/ClipboardTextField.kt @@ -76,8 +76,8 @@ fun ClipboardTextField( private fun action( focusManager: FocusManager?, onConfirm: (String) -> Unit, - value: String + value: String, ): KeyboardActionScope.() -> Unit = { focusManager?.clearFocus() onConfirm(value) -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/component/base/CurlyCornerShape.kt b/app/src/main/java/me/ash/reader/ui/component/base/CurlyCornerShape.kt index 0cf4df3..14b898c 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/CurlyCornerShape.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/CurlyCornerShape.kt @@ -22,13 +22,14 @@ class CurlyCornerShape( bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize ) { + private fun sineCircleXYatAngle( d1: Double, d2: Double, d3: Double, d4: Double, d5: Double, - i: Int + i: Int, ): List = (i.toDouble() * d5).run { listOf( (sin(this) * d4 + d3) * cos(d5) + d1, @@ -42,7 +43,7 @@ class CurlyCornerShape( topEnd: Float, bottomEnd: Float, bottomStart: Float, - layoutDirection: LayoutDirection + layoutDirection: LayoutDirection, ): Outline { val d = 2.0 val r2: Double = size.width / d @@ -73,11 +74,11 @@ class CurlyCornerShape( topStart: CornerSize, topEnd: CornerSize, bottomEnd: CornerSize, - bottomStart: CornerSize + bottomStart: CornerSize, ) = RoundedCornerShape( topStart = topStart, topEnd = topEnd, bottomEnd = bottomEnd, bottomStart = bottomStart ) -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/component/base/DynamicSVGImage.kt b/app/src/main/java/me/ash/reader/ui/component/base/DynamicSVGImage.kt index 1240cd5..4f747d7 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/DynamicSVGImage.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/DynamicSVGImage.kt @@ -9,7 +9,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.unit.IntSize import com.caverock.androidsvg.SVG -import me.ash.reader.data.preference.LocalDarkTheme +import me.ash.reader.data.model.preference.LocalDarkTheme import me.ash.reader.ui.svg.parseDynamicColor import me.ash.reader.ui.theme.palette.LocalTonalPalettes @@ -49,4 +49,4 @@ fun DynamicSVGImage( ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/component/base/RYExtensibleVisibility.kt b/app/src/main/java/me/ash/reader/ui/component/base/RYExtensibleVisibility.kt index b1485a5..609c09b 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/RYExtensibleVisibility.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/RYExtensibleVisibility.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.Composable @Composable fun RYExtensibleVisibility( visible: Boolean, - content: @Composable AnimatedVisibilityScope.() -> Unit + content: @Composable AnimatedVisibilityScope.() -> Unit, ) { AnimatedVisibility( visible = visible, @@ -12,4 +12,4 @@ fun RYExtensibleVisibility( exit = fadeOut() + shrinkVertically(), content = content, ) -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/component/base/RYSwitch.kt b/app/src/main/java/me/ash/reader/ui/component/base/RYSwitch.kt index ce9b972..212f371 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/RYSwitch.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/RYSwitch.kt @@ -35,7 +35,7 @@ fun RYSwitch( modifier: Modifier = Modifier, activated: Boolean, enable: Boolean = true, - onClick: (() -> Unit)? = null + onClick: (() -> Unit)? = null, ) { val tonalPalettes = LocalTonalPalettes.current @@ -74,7 +74,7 @@ fun SwitchHeadline( activated: Boolean, onClick: () -> Unit, title: String, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { val tonalPalettes = LocalTonalPalettes.current diff --git a/app/src/main/java/me/ash/reader/ui/component/base/Tips.kt b/app/src/main/java/me/ash/reader/ui/component/base/Tips.kt index 4f9d519..3e5da72 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/Tips.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/Tips.kt @@ -21,7 +21,7 @@ fun Tips( Column( modifier = modifier .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 16.dp), + .padding(horizontal = 24.dp, vertical = 16.dp), ) { Icon( imageVector = Icons.Outlined.Info, @@ -35,4 +35,4 @@ fun Tips( color = MaterialTheme.colorScheme.onSurfaceVariant, ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/component/base/WebView.kt b/app/src/main/java/me/ash/reader/ui/component/base/WebView.kt index ae3cf06..860accb 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/WebView.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/WebView.kt @@ -18,7 +18,7 @@ const val INJECTION_TOKEN = "/android_asset_font/" fun WebView( modifier: Modifier = Modifier, content: String, - onReceivedError: (error: WebResourceError?) -> Unit = {} + onReceivedError: (error: WebResourceError?) -> Unit = {}, ) { val context = LocalContext.current val color = MaterialTheme.colorScheme.onSurfaceVariant.toArgb() @@ -28,7 +28,7 @@ fun WebView( override fun shouldInterceptRequest( view: WebView?, - url: String? + url: String?, ): WebResourceResponse? { if (url != null && url.contains(INJECTION_TOKEN)) { try { @@ -63,7 +63,7 @@ fun WebView( override fun shouldOverrideUrlLoading( view: WebView?, - request: WebResourceRequest? + request: WebResourceRequest?, ): Boolean { if (null == request?.url) return false val url = request.url.toString() @@ -79,7 +79,7 @@ fun WebView( override fun onReceivedError( view: WebView?, request: WebResourceRequest?, - error: WebResourceError? + error: WebResourceError?, ) { super.onReceivedError(view, request, error) onReceivedError(error) @@ -88,7 +88,7 @@ fun WebView( override fun onReceivedSslError( view: WebView?, handler: SslErrorHandler?, - error: SslError? + error: SslError?, ) { handler?.cancel() } @@ -213,4 +213,4 @@ h1,h2,h3,h4,h5,h6,figure,br { .element::-webkit-scrollbar { width: 0 !important } -""" \ No newline at end of file +""" diff --git a/app/src/main/java/me/ash/reader/ui/component/reader/AnnotatedString.kt b/app/src/main/java/me/ash/reader/ui/component/reader/AnnotatedString.kt index dbadc3e..1b570ee 100644 --- a/app/src/main/java/me/ash/reader/ui/component/reader/AnnotatedString.kt +++ b/app/src/main/java/me/ash/reader/ui/component/reader/AnnotatedString.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle class AnnotatedParagraphStringBuilder { + // Private for a reason private val builder: AnnotatedString.Builder = AnnotatedString.Builder() @@ -60,7 +61,7 @@ class AnnotatedParagraphStringBuilder { builder.pushStringAnnotation(tag = tag, annotation = annotation) fun pushComposableStyle( - style: @Composable () -> SpanStyle + style: @Composable () -> SpanStyle, ): Int { composableStyles.add( ComposableStyleWithStartEnd( @@ -72,7 +73,7 @@ class AnnotatedParagraphStringBuilder { } fun popComposableStyle( - index: Int + index: Int, ) { poppedComposableStyles.add( composableStyles.removeAt(index).copy(end = builder.length) @@ -122,20 +123,25 @@ fun AnnotatedParagraphStringBuilder.ensureDoubleNewline() { lastTwoChars.isEmpty() -> { // Nothing to do } + length == 1 && lastTwoChars.peekLatest()?.isWhitespace() == true -> { // Nothing to do } + length == 2 && lastTwoChars.peekLatest()?.isWhitespace() == true && lastTwoChars.peekSecondLatest()?.isWhitespace() == true -> { // Nothing to do } + lastTwoChars.peekLatest() == '\n' && lastTwoChars.peekSecondLatest() == '\n' -> { // Nothing to do } + lastTwoChars.peekLatest() == '\n' -> { append('\n') } + else -> { append("\n\n") } @@ -147,12 +153,15 @@ private fun AnnotatedParagraphStringBuilder.ensureSingleNewline() { lastTwoChars.isEmpty() -> { // Nothing to do } + length == 1 && lastTwoChars.peekLatest()?.isWhitespace() == true -> { // Nothing to do } + lastTwoChars.peekLatest() == '\n' -> { // Nothing to do } + else -> { append('\n') } @@ -187,5 +196,5 @@ private fun List.peekSecondLatest(): T? { data class ComposableStyleWithStartEnd( val style: @Composable () -> SpanStyle, val start: Int, - val end: Int = -1 + val end: Int = -1, ) diff --git a/app/src/main/java/me/ash/reader/ui/component/reader/HtmlToComposable.kt b/app/src/main/java/me/ash/reader/ui/component/reader/HtmlToComposable.kt index 1d98501..e9c172b 100644 --- a/app/src/main/java/me/ash/reader/ui/component/reader/HtmlToComposable.kt +++ b/app/src/main/java/me/ash/reader/ui/component/reader/HtmlToComposable.kt @@ -206,6 +206,7 @@ private fun TextComposer.appendTextChildren( } } } + is Element -> { val element = node when (element.tagName()) { @@ -232,6 +233,7 @@ private fun TextComposer.appendTextChildren( } } } + "br" -> append('\n') "h1" -> { withParagraph { @@ -242,6 +244,7 @@ private fun TextComposer.appendTextChildren( } } } + "h2" -> { withParagraph { withComposableStyle( @@ -251,6 +254,7 @@ private fun TextComposer.appendTextChildren( } } } + "h3" -> { withParagraph { withComposableStyle( @@ -260,6 +264,7 @@ private fun TextComposer.appendTextChildren( } } } + "h4" -> { withParagraph { withComposableStyle( @@ -269,6 +274,7 @@ private fun TextComposer.appendTextChildren( } } } + "h5" -> { withParagraph { withComposableStyle( @@ -278,6 +284,7 @@ private fun TextComposer.appendTextChildren( } } } + "h6" -> { withParagraph { withComposableStyle( @@ -287,6 +294,7 @@ private fun TextComposer.appendTextChildren( } } } + "strong", "b" -> { withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { appendTextChildren( @@ -298,6 +306,7 @@ private fun TextComposer.appendTextChildren( ) } } + "i", "em", "cite", "dfn" -> { withStyle(SpanStyle(fontStyle = FontStyle.Italic)) { appendTextChildren( @@ -309,6 +318,7 @@ private fun TextComposer.appendTextChildren( ) } } + "tt" -> { withStyle(SpanStyle(fontFamily = FontFamily.Monospace)) { appendTextChildren( @@ -320,6 +330,7 @@ private fun TextComposer.appendTextChildren( ) } } + "u" -> { withStyle(SpanStyle(textDecoration = TextDecoration.Underline)) { appendTextChildren( @@ -331,6 +342,7 @@ private fun TextComposer.appendTextChildren( ) } } + "sup" -> { withStyle(SpanStyle(baselineShift = BaselineShift.Superscript)) { appendTextChildren( @@ -342,6 +354,7 @@ private fun TextComposer.appendTextChildren( ) } } + "sub" -> { withStyle(SpanStyle(baselineShift = BaselineShift.Subscript)) { appendTextChildren( @@ -353,6 +366,7 @@ private fun TextComposer.appendTextChildren( ) } } + "font" -> { val fontFamily: FontFamily? = element.attr("face")?.asFontFamily() withStyle(SpanStyle(fontFamily = fontFamily)) { @@ -365,6 +379,7 @@ private fun TextComposer.appendTextChildren( ) } } + "pre" -> { appendTextChildren( element.childNodes(), @@ -375,6 +390,7 @@ private fun TextComposer.appendTextChildren( baseUrl = baseUrl, ) } + "code" -> { if (element.parent()?.tagName() == "pre") { terminateCurrentText() @@ -400,6 +416,7 @@ private fun TextComposer.appendTextChildren( } } } + "blockquote" -> { withParagraph { withComposableStyle( @@ -415,6 +432,7 @@ private fun TextComposer.appendTextChildren( } } } + "a" -> { withComposableStyle( style = { linkTextStyle().toSpanStyle() } @@ -430,6 +448,7 @@ private fun TextComposer.appendTextChildren( } } } + "img" -> { val imageCandidates = getImageSource(baseUrl, element) if (imageCandidates.hasImage) { @@ -502,6 +521,7 @@ private fun TextComposer.appendTextChildren( } } } + "ul" -> { element.children() .filter { it.tagName() == "li" } @@ -519,6 +539,7 @@ private fun TextComposer.appendTextChildren( } } } + "ol" -> { element.children() .filter { it.tagName() == "li" } @@ -536,6 +557,7 @@ private fun TextComposer.appendTextChildren( } } } + "table" -> { appendTable { /* @@ -581,6 +603,7 @@ private fun TextComposer.appendTextChildren( append("\n\n") } } + "iframe" -> { val video: Video? = getVideo(element.attr("abs:src")) @@ -629,9 +652,11 @@ private fun TextComposer.appendTextChildren( } } } + "video" -> { // not implemented yet. remember to disable selection } + else -> { appendTextChildren( nodes = element.childNodes(), @@ -707,8 +732,9 @@ internal fun getImageSource(baseUrl: String, element: Element) = ImageCandidates internal class ImageCandidates( val baseUrl: String, val srcSet: String, - val absSrc: String + val absSrc: String, ) { + val hasImage: Boolean = srcSet.isNotBlank() || absSrc.isNotBlank() /** @@ -728,9 +754,11 @@ internal class ImageCandidates( descriptor.endsWith("w", ignoreCase = true) -> { descriptor.substringBefore("w").toFloat() / maxSize.width.pxOrElse { 1 } } + descriptor.endsWith("x", ignoreCase = true) -> { descriptor.substringBefore("x").toFloat() / pixelDensity } + else -> { return@fold acc } diff --git a/app/src/main/java/me/ash/reader/ui/component/reader/TextComposer.kt b/app/src/main/java/me/ash/reader/ui/component/reader/TextComposer.kt index f3db0b1..b1c1c8f 100644 --- a/app/src/main/java/me/ash/reader/ui/component/reader/TextComposer.kt +++ b/app/src/main/java/me/ash/reader/ui/component/reader/TextComposer.kt @@ -24,8 +24,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.text.SpanStyle class TextComposer( - val paragraphEmitter: (AnnotatedParagraphStringBuilder) -> Unit + val paragraphEmitter: (AnnotatedParagraphStringBuilder) -> Unit, ) { + val spanStack: MutableList = mutableListOf() // The identity of this will change - do not reference it in blocks @@ -48,6 +49,7 @@ class TextComposer( tag = span.tag, annotation = span.annotation ) + is SpanWithComposableStyle -> builder.pushComposableStyle(span.spanStyle) } } @@ -75,8 +77,8 @@ class TextComposer( link: String? = null, onLinkClick: (String) -> Unit, block: ( - onClick: (() -> Unit)? - ) -> R + onClick: (() -> Unit)?, + ) -> R, ): R { val url = link ?: findClosestLink() //builder.ensureDoubleNewline() @@ -117,7 +119,7 @@ class TextComposer( } inline fun TextComposer.withParagraph( - crossinline block: TextComposer.() -> R + crossinline block: TextComposer.() -> R, ): R { ensureDoubleNewline() return block(this) @@ -125,7 +127,7 @@ inline fun TextComposer.withParagraph( inline fun TextComposer.withStyle( style: SpanStyle, - crossinline block: TextComposer.() -> R + crossinline block: TextComposer.() -> R, ): R { spanStack.add(SpanWithStyle(style)) val index = pushStyle(style) @@ -139,7 +141,7 @@ inline fun TextComposer.withStyle( inline fun TextComposer.withComposableStyle( noinline style: @Composable () -> SpanStyle, - crossinline block: TextComposer.() -> R + crossinline block: TextComposer.() -> R, ): R { spanStack.add(SpanWithComposableStyle(style)) val index = pushComposableStyle(style) @@ -154,7 +156,7 @@ inline fun TextComposer.withComposableStyle( inline fun TextComposer.withAnnotation( tag: String, annotation: String, - crossinline block: TextComposer.() -> R + crossinline block: TextComposer.() -> R, ): R { spanStack.add(SpanWithAnnotation(tag = tag, annotation = annotation)) val index = pushStringAnnotation(tag = tag, annotation = annotation) @@ -169,14 +171,14 @@ inline fun TextComposer.withAnnotation( sealed class Span data class SpanWithStyle( - val spanStyle: SpanStyle + val spanStyle: SpanStyle, ) : Span() data class SpanWithAnnotation( val tag: String, - val annotation: String + val annotation: String, ) : Span() data class SpanWithComposableStyle( - val spanStyle: @Composable () -> SpanStyle + val spanStyle: @Composable () -> SpanStyle, ) : Span() diff --git a/app/src/main/java/me/ash/reader/ui/component/reader/VideoTagHunter.kt b/app/src/main/java/me/ash/reader/ui/component/reader/VideoTagHunter.kt index 084ce6a..3f5e6a6 100644 --- a/app/src/main/java/me/ash/reader/ui/component/reader/VideoTagHunter.kt +++ b/app/src/main/java/me/ash/reader/ui/component/reader/VideoTagHunter.kt @@ -44,8 +44,9 @@ data class Video( val src: String, val imageUrl: String, // Youtube needs a different link than embed links - val link: String + val link: String, ) { + val width: Int get() = 480 diff --git a/app/src/main/java/me/ash/reader/ui/ext/ContextExt.kt b/app/src/main/java/me/ash/reader/ui/ext/ContextExt.kt index 8b084ba..b685970 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/ContextExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/ContextExt.kt @@ -9,8 +9,8 @@ import android.util.Log import android.widget.Toast import androidx.core.content.FileProvider import me.ash.reader.R -import me.ash.reader.data.model.Version -import me.ash.reader.data.model.toVersion +import me.ash.reader.data.model.general.Version +import me.ash.reader.data.model.general.toVersion import java.io.File fun Context.findActivity(): Activity? = when (this) { 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 52ca3b1..55cd3e1 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 @@ -66,180 +66,216 @@ fun DataStore.get(dataStoreKeys: DataStoreKeys): T? { } sealed class DataStoreKeys { + abstract val key: Preferences.Key object IsFirstLaunch : DataStoreKeys() { + override val key: Preferences.Key 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() = stringPreferencesKey("newVersionSizeString") } 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") } object CurrentAccountType : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("currentAccountType") } object ThemeIndex : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("themeIndex") } object CustomPrimaryColor : DataStoreKeys() { + override val key: Preferences.Key get() = stringPreferencesKey("customPrimaryColor") } object DarkTheme : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("darkTheme") } object AmoledDarkTheme : DataStoreKeys() { + override val key: Preferences.Key get() = booleanPreferencesKey("amoledDarkTheme") } object FeedsFilterBarStyle : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("feedsFilterBarStyle") } object FeedsFilterBarFilled : DataStoreKeys() { + override val key: Preferences.Key get() = booleanPreferencesKey("feedsFilterBarFilled") } object FeedsFilterBarPadding : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("feedsFilterBarPadding") } object FeedsFilterBarTonalElevation : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("feedsFilterBarTonalElevation") } object FeedsTopBarTonalElevation : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("feedsTopBarTonalElevation") } object FeedsGroupListExpand : DataStoreKeys() { + override val key: Preferences.Key get() = booleanPreferencesKey("feedsGroupListExpand") } object FeedsGroupListTonalElevation : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("feedsGroupListTonalElevation") } object FlowFilterBarStyle : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("flowFilterBarStyle") } object FlowFilterBarFilled : DataStoreKeys() { + override val key: Preferences.Key get() = booleanPreferencesKey("flowFilterBarFilled") } object FlowFilterBarPadding : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("flowFilterBarPadding") } object FlowFilterBarTonalElevation : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("flowFilterBarTonalElevation") } object FlowTopBarTonalElevation : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("flowTopBarTonalElevation") } object FlowArticleListFeedIcon : DataStoreKeys() { + override val key: Preferences.Key get() = booleanPreferencesKey("flowArticleListFeedIcon") } object FlowArticleListFeedName : DataStoreKeys() { + override val key: Preferences.Key get() = booleanPreferencesKey("flowArticleListFeedName") } object FlowArticleListImage : DataStoreKeys() { + override val key: Preferences.Key get() = booleanPreferencesKey("flowArticleListImage") } object FlowArticleListDesc : DataStoreKeys() { + override val key: Preferences.Key get() = booleanPreferencesKey("flowArticleListDesc") } object FlowArticleListTime : DataStoreKeys() { + override val key: Preferences.Key get() = booleanPreferencesKey("flowArticleListTime") } object FlowArticleListDateStickyHeader : DataStoreKeys() { + override val key: Preferences.Key get() = booleanPreferencesKey("flowArticleListDateStickyHeader") } object FlowArticleListTonalElevation : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("flowArticleListTonalElevation") } object InitialPage : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("initialPage") } object InitialFilter : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("initialFilter") } object Languages : DataStoreKeys() { + override val key: Preferences.Key get() = intPreferencesKey("languages") } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/ext/DateExt.kt b/app/src/main/java/me/ash/reader/ui/ext/DateExt.kt index e609595..3eaa00d 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/DateExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/DateExt.kt @@ -19,6 +19,7 @@ fun Date.formatAsString( onlyHourMinute == true -> { SimpleDateFormat("HH:mm", locale).format(this) } + atHourMinute == true -> { context.getString( R.string.date_at_time, @@ -26,6 +27,7 @@ fun Date.formatAsString( SimpleDateFormat("HH:mm", locale).format(this), ) } + else -> { df.format(this).run { when (this) { @@ -35,7 +37,9 @@ fun Date.formatAsString( time = Date() add(Calendar.DAY_OF_MONTH, -1) }.time - ) -> context.getString(R.string.yesterday) + ), + -> context.getString(R.string.yesterday) + else -> this } } @@ -52,7 +56,7 @@ private fun String.parseToDate( "yyyy/MM/dd", "yyyy年MM月dd日", "yyyy MM dd", - ) + ), ): Date? { val df = SimpleDateFormat() for (pattern in patterns) { @@ -64,4 +68,4 @@ private fun String.parseToDate( } } return null -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/ext/NavGraphBuilderExt.kt b/app/src/main/java/me/ash/reader/ui/ext/NavGraphBuilderExt.kt index 12e1775..47143fe 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/NavGraphBuilderExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/NavGraphBuilderExt.kt @@ -22,7 +22,7 @@ fun NavGraphBuilder.animatedComposable( route: String, arguments: List = emptyList(), deepLinks: List = emptyList(), - content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit + content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit, ) = composable( route = route, arguments = arguments, diff --git a/app/src/main/java/me/ash/reader/ui/ext/PagerStateExt.kt b/app/src/main/java/me/ash/reader/ui/ext/PagerStateExt.kt index c2fedcd..1dba85b 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/PagerStateExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/PagerStateExt.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.launch fun PagerState.animateScrollToPage( scope: CoroutineScope, targetPage: Int, - callback: () -> Unit = {} + callback: () -> Unit = {}, ) { scope.launch { if (pageCount > targetPage) { @@ -17,4 +17,4 @@ fun PagerState.animateScrollToPage( callback() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/ext/ScrollbarsExt.kt b/app/src/main/java/me/ash/reader/ui/ext/ScrollbarsExt.kt index d7c7184..e683d96 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/ScrollbarsExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/ScrollbarsExt.kt @@ -73,28 +73,28 @@ import kotlinx.coroutines.flow.collectLatest fun Modifier.drawHorizontalScrollbar( state: ScrollState, - reverseScrolling: Boolean = false + reverseScrolling: Boolean = false, ): Modifier = drawScrollbar(state, Orientation.Horizontal, reverseScrolling) fun Modifier.drawVerticalScrollbar( state: ScrollState, - reverseScrolling: Boolean = false + reverseScrolling: Boolean = false, ): Modifier = drawScrollbar(state, Orientation.Vertical, reverseScrolling) fun Modifier.drawHorizontalScrollbar( state: LazyListState, - reverseScrolling: Boolean = false + reverseScrolling: Boolean = false, ): Modifier = drawScrollbar(state, Orientation.Horizontal, reverseScrolling) fun Modifier.drawVerticalScrollbar( state: LazyListState, - reverseScrolling: Boolean = false + reverseScrolling: Boolean = false, ): Modifier = drawScrollbar(state, Orientation.Vertical, reverseScrolling) private fun Modifier.drawScrollbar( state: ScrollState, orientation: Orientation, - reverseScrolling: Boolean + reverseScrolling: Boolean, ): Modifier = drawScrollbar( orientation, reverseScrolling ) { reverseDirection, atEnd, thickness, color, alpha -> @@ -116,7 +116,7 @@ private fun Modifier.drawScrollbar( private fun Modifier.drawScrollbar( state: LazyListState, orientation: Orientation, - reverseScrolling: Boolean + reverseScrolling: Boolean, ): Modifier = drawScrollbar( orientation, reverseScrolling ) { reverseDirection, atEnd, thickness, color, alpha -> @@ -153,7 +153,7 @@ private fun CacheDrawScope.onDrawScrollbar( color: Color, alpha: () -> Float, thumbSize: Float, - startOffset: Float + startOffset: Float, ): DrawScope.() -> Unit { val topLeft = if (orientation == Orientation.Horizontal) { Offset( @@ -196,8 +196,8 @@ private fun Modifier.drawScrollbar( atEnd: Boolean, thickness: Float, color: Color, - alpha: () -> Float - ) -> DrawResult + alpha: () -> Float, + ) -> DrawResult, ): Modifier = composed { val scrolled = remember { MutableSharedFlow( @@ -210,7 +210,7 @@ private fun Modifier.drawScrollbar( override fun onPostScroll( consumed: Offset, available: Offset, - source: NestedScrollSource + source: NestedScrollSource, ): Offset { val delta = if (orientation == Orientation.Horizontal) consumed.x else consumed.y if (delta != 0f) scrolled.tryEmit(Unit) diff --git a/app/src/main/java/me/ash/reader/ui/ext/StateFlowExt.kt b/app/src/main/java/me/ash/reader/ui/ext/StateFlowExt.kt index f968cf5..f1e12f1 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/StateFlowExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/StateFlowExt.kt @@ -9,11 +9,11 @@ import kotlin.coroutines.CoroutineContext @Composable fun StateFlow.collectAsStateValue( - context: CoroutineContext = Dispatchers.Default + context: CoroutineContext = Dispatchers.Default, ): T = collectAsState(context).value @Composable fun Flow.collectAsStateValue( initial: R, - context: CoroutineContext = Dispatchers.Default -): R = collectAsState(initial, context).value \ No newline at end of file + context: CoroutineContext = Dispatchers.Default, +): R = collectAsState(initial, context).value diff --git a/app/src/main/java/me/ash/reader/ui/page/common/ExtraName.kt b/app/src/main/java/me/ash/reader/ui/page/common/ExtraName.kt index 87abe5a..c7e0816 100644 --- a/app/src/main/java/me/ash/reader/ui/page/common/ExtraName.kt +++ b/app/src/main/java/me/ash/reader/ui/page/common/ExtraName.kt @@ -1,5 +1,6 @@ package me.ash.reader.ui.page.common object ExtraName { + const val ARTICLE_ID: String = "article.id" -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt b/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt index 92ca3b6..4f2da16 100644 --- a/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt +++ b/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt @@ -12,8 +12,8 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.google.accompanist.navigation.animation.AnimatedNavHost import com.google.accompanist.navigation.animation.rememberAnimatedNavController import com.google.accompanist.systemuicontroller.rememberSystemUiController -import me.ash.reader.data.model.Filter -import me.ash.reader.data.preference.LocalDarkTheme +import me.ash.reader.data.model.general.Filter +import me.ash.reader.data.model.preference.LocalDarkTheme import me.ash.reader.ui.ext.* import me.ash.reader.ui.page.home.HomeViewModel import me.ash.reader.ui.page.home.feeds.FeedsPage @@ -149,4 +149,4 @@ fun HomeEntry( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/page/common/NotificationGroupName.kt b/app/src/main/java/me/ash/reader/ui/page/common/NotificationGroupName.kt index df5c194..eed7725 100644 --- a/app/src/main/java/me/ash/reader/ui/page/common/NotificationGroupName.kt +++ b/app/src/main/java/me/ash/reader/ui/page/common/NotificationGroupName.kt @@ -1,5 +1,6 @@ package me.ash.reader.ui.page.common object NotificationGroupName { + const val ARTICLE_UPDATE: String = "article.update" -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt b/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt index b0f6eba..1d7be0c 100644 --- a/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt +++ b/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt @@ -1,6 +1,7 @@ package me.ash.reader.ui.page.common object RouteName { + // Startup const val STARTUP = "startup" @@ -26,4 +27,4 @@ object RouteName { // Tips & Support const val TIPS_AND_SUPPORT = "tips_and_support" -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt index 56bebff..c96eaf5 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt @@ -1,19 +1,23 @@ package me.ash.reader.ui.page.home import androidx.lifecycle.ViewModel -import androidx.paging.* +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.cachedIn import androidx.work.WorkManager import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* -import me.ash.reader.data.entity.Feed -import me.ash.reader.data.entity.Group -import me.ash.reader.data.model.Filter +import me.ash.reader.data.model.article.ArticleFlowItem +import me.ash.reader.data.model.article.mapPagingFlowItem +import me.ash.reader.data.model.feed.Feed +import me.ash.reader.data.model.general.Filter +import me.ash.reader.data.model.group.Group import me.ash.reader.data.module.ApplicationScope import me.ash.reader.data.repository.RssRepository import me.ash.reader.data.repository.StringsRepository import me.ash.reader.data.repository.SyncWorker -import me.ash.reader.ui.page.home.flow.FlowItemView import javax.inject.Inject @HiltViewModel @@ -24,13 +28,14 @@ class HomeViewModel @Inject constructor( private val applicationScope: CoroutineScope, private val workManager: WorkManager, ) : ViewModel() { + private val _homeUiState = MutableStateFlow(HomeUiState()) val homeUiState: StateFlow = _homeUiState.asStateFlow() private val _filterUiState = MutableStateFlow(FilterState()) val filterUiState = _filterUiState.asStateFlow() - val syncWorkLiveData = workManager.getWorkInfoByIdLiveData(SyncWorker.UUID) + val syncWorkLiveData = workManager.getWorkInfoByIdLiveData(SyncWorker.uuid) fun sync() { rssRepository.get().doSync() @@ -73,35 +78,14 @@ class HomeViewModel @Inject constructor( ) } }.flow.map { - it.map { - FlowItemView.Article(it.apply { - article.dateString = stringsRepository.formatAsString( - date = article.date, - onlyHourMinute = true - ) - }) - }.insertSeparators { before, after -> - val beforeDate = - stringsRepository.formatAsString(before?.articleWithFeed?.article?.date) - val afterDate = - stringsRepository.formatAsString(after?.articleWithFeed?.article?.date) - if (beforeDate != afterDate) { - afterDate?.let { FlowItemView.Date(it, beforeDate != null) } - } else { - null - } - } + it.mapPagingFlowItem(stringsRepository) }.cachedIn(applicationScope) ) } } fun inputSearchContent(content: String) { - _homeUiState.update { - it.copy( - searchContent = content, - ) - } + _homeUiState.update { it.copy(searchContent = content) } fetchArticles() } } @@ -113,6 +97,6 @@ data class FilterState( ) data class HomeUiState( - val pagingData: Flow> = emptyFlow(), + val pagingData: Flow> = emptyFlow(), val searchContent: String = "", -) \ No newline at end of file +) diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedItem.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedItem.kt index bbd3deb..098d656 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedItem.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedItem.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.platform.LocalView import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel -import me.ash.reader.data.entity.Feed +import me.ash.reader.data.model.feed.Feed import me.ash.reader.ui.component.FeedIcon import me.ash.reader.ui.page.home.feeds.drawer.feed.FeedOptionViewModel import me.ash.reader.ui.theme.ShapeBottom32 diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultView.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedOptionView.kt similarity index 98% rename from app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultView.kt rename to app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedOptionView.kt index cadeb24..ef5cbdd 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultView.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedOptionView.kt @@ -1,4 +1,4 @@ -package me.ash.reader.ui.page.home.feeds.subscribe +package me.ash.reader.ui.page.home.feeds import androidx.compose.animation.animateContentSize import androidx.compose.foundation.* @@ -25,13 +25,13 @@ import com.google.accompanist.flowlayout.FlowCrossAxisAlignment import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.MainAxisAlignment import me.ash.reader.R -import me.ash.reader.data.entity.Group +import me.ash.reader.data.model.group.Group import me.ash.reader.ui.component.base.RYSelectionChip import me.ash.reader.ui.component.base.Subtitle import me.ash.reader.ui.theme.palette.alwaysLight @Composable -fun ResultView( +fun FeedOptionView( modifier: Modifier = Modifier, link: String = "", groups: List = emptyList(), @@ -248,4 +248,4 @@ private fun NewGroupButton(onAddNewGroup: () -> Unit) { tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f), ) } -} \ No newline at end of file +} 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 8d2c455..8ea6f09 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 @@ -26,8 +26,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import me.ash.reader.R -import me.ash.reader.data.model.getName -import me.ash.reader.data.preference.* +import me.ash.reader.data.model.preference.* import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing import me.ash.reader.ui.component.FilterBar import me.ash.reader.ui.component.base.* @@ -183,7 +182,7 @@ fun FeedsPage( } item { Banner( - title = filterUiState.filter.getName(), + title = filterUiState.filter.toName(), desc = importantSum, icon = filterUiState.filter.iconOutline, action = { @@ -238,6 +237,7 @@ fun FeedsPage( ) } } + is GroupFeedsView.Feed -> { FeedItem( feed = groupWithFeed.feed, @@ -299,4 +299,4 @@ private fun filterChange( launchSingleTop = true } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt index d885ad3..cd560d9 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt @@ -9,9 +9,9 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import me.ash.reader.R -import me.ash.reader.data.entity.Account -import me.ash.reader.data.module.DispatcherDefault -import me.ash.reader.data.module.DispatcherIO +import me.ash.reader.data.model.account.Account +import me.ash.reader.data.module.DefaultDispatcher +import me.ash.reader.data.module.IODispatcher import me.ash.reader.data.repository.AccountRepository import me.ash.reader.data.repository.OpmlRepository import me.ash.reader.data.repository.RssRepository @@ -25,26 +25,23 @@ class FeedsViewModel @Inject constructor( private val rssRepository: RssRepository, private val opmlRepository: OpmlRepository, private val stringsRepository: StringsRepository, - @DispatcherDefault - private val dispatcherDefault: CoroutineDispatcher, - @DispatcherIO - private val dispatcherIO: CoroutineDispatcher, + @DefaultDispatcher + private val defaultDispatcher: CoroutineDispatcher, + @IODispatcher + private val ioDispatcher: CoroutineDispatcher, ) : ViewModel() { + private val _feedsUiState = MutableStateFlow(FeedsUiState()) val feedsUiState: StateFlow = _feedsUiState.asStateFlow() fun fetchAccount() { - viewModelScope.launch(dispatcherIO) { - _feedsUiState.update { - it.copy( - account = accountRepository.getCurrentAccount() - ) - } + viewModelScope.launch(ioDispatcher) { + _feedsUiState.update { it.copy(account = accountRepository.getCurrentAccount()) } } } fun exportAsOpml(callback: (String) -> Unit = {}) { - viewModelScope.launch(dispatcherDefault) { + viewModelScope.launch(defaultDispatcher) { try { callback(opmlRepository.saveToString()) } catch (e: Exception) { @@ -71,7 +68,7 @@ class FeedsViewModel @Inject constructor( this ) } - }.flowOn(dispatcherDefault), + }.flowOn(defaultDispatcher), groupWithFeedList = combine( rssRepository.get().pullImportant(isStarred, isUnread), rssRepository.get().pullFeeds() @@ -107,7 +104,7 @@ class FeedsViewModel @Inject constructor( ) } }.flatten() - }.flowOn(dispatcherDefault), + }.flowOn(defaultDispatcher), ) } } @@ -122,6 +119,6 @@ data class FeedsUiState( ) sealed class GroupFeedsView { - class Group(val group: me.ash.reader.data.entity.Group) : GroupFeedsView() - class Feed(val feed: me.ash.reader.data.entity.Feed) : GroupFeedsView() -} \ No newline at end of file + class Group(val group: me.ash.reader.data.model.group.Group) : GroupFeedsView() + class Feed(val feed: me.ash.reader.data.model.feed.Feed) : GroupFeedsView() +} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt index 9adcb0c..4ff197b 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt @@ -24,7 +24,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import me.ash.reader.R -import me.ash.reader.data.entity.Group +import me.ash.reader.data.model.group.Group import me.ash.reader.ui.page.home.feeds.drawer.group.GroupOptionViewModel import me.ash.reader.ui.theme.Shape32 import me.ash.reader.ui.theme.ShapeTop32 diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionDrawer.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionDrawer.kt index f6f47b0..1d2f968 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionDrawer.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionDrawer.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.* import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CreateNewFolder -import androidx.compose.material.icons.outlined.Edit import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -21,14 +20,16 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import kotlinx.coroutines.launch import me.ash.reader.R +import me.ash.reader.ui.component.ChangeUrlDialog import me.ash.reader.ui.component.FeedIcon +import me.ash.reader.ui.component.RenameDialog import me.ash.reader.ui.component.base.BottomDrawer import me.ash.reader.ui.component.base.TextFieldDialog import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.openURL import me.ash.reader.ui.ext.roundClick import me.ash.reader.ui.ext.showToast -import me.ash.reader.ui.page.home.feeds.subscribe.ResultView +import me.ash.reader.ui.page.home.feeds.FeedOptionView @OptIn(ExperimentalMaterialApi::class) @Composable @@ -78,7 +79,7 @@ fun FeedOptionDrawer( ) } Spacer(modifier = Modifier.height(16.dp)) - ResultView( + FeedOptionView( link = feed?.url ?: stringResource(R.string.unknown), groups = feedOptionUiState.groups, selectedAllowNotificationPreset = feedOptionUiState.feed?.isNotification @@ -140,12 +141,9 @@ fun FeedOptionDrawer( } ) - TextFieldDialog( + RenameDialog( visible = feedOptionUiState.renameDialogVisible, - title = stringResource(R.string.rename), - icon = Icons.Outlined.Edit, value = feedOptionUiState.newName, - placeholder = stringResource(R.string.name), onValueChange = { feedOptionViewModel.inputNewName(it) }, @@ -159,12 +157,9 @@ fun FeedOptionDrawer( } ) - TextFieldDialog( + ChangeUrlDialog( visible = feedOptionUiState.changeUrlDialogVisible, - title = stringResource(R.string.change_url), - icon = Icons.Outlined.Edit, value = feedOptionUiState.newUrl, - placeholder = stringResource(R.string.feed_url_placeholder), onValueChange = { feedOptionViewModel.inputNewUrl(it) }, @@ -176,4 +171,4 @@ fun FeedOptionDrawer( feedOptionViewModel.hideDrawer(scope) } ) -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionViewModel.kt index 6ed3941..88856ef 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionViewModel.kt @@ -14,10 +14,10 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import me.ash.reader.data.entity.Feed -import me.ash.reader.data.entity.Group -import me.ash.reader.data.module.DispatcherIO -import me.ash.reader.data.module.DispatcherMain +import me.ash.reader.data.model.feed.Feed +import me.ash.reader.data.model.group.Group +import me.ash.reader.data.module.IODispatcher +import me.ash.reader.data.module.MainDispatcher import me.ash.reader.data.repository.RssRepository import javax.inject.Inject @@ -25,22 +25,19 @@ import javax.inject.Inject @HiltViewModel class FeedOptionViewModel @Inject constructor( private val rssRepository: RssRepository, - @DispatcherMain - private val dispatcherMain: CoroutineDispatcher, - @DispatcherIO - private val dispatcherIO: CoroutineDispatcher, + @MainDispatcher + private val mainDispatcher: CoroutineDispatcher, + @IODispatcher + private val ioDispatcher: CoroutineDispatcher, ) : ViewModel() { + private val _feedOptionUiState = MutableStateFlow(FeedOptionUiState()) val feedOptionUiState: StateFlow = _feedOptionUiState.asStateFlow() init { - viewModelScope.launch(dispatcherIO) { + viewModelScope.launch(ioDispatcher) { rssRepository.get().pullGroups().collect { groups -> - _feedOptionUiState.update { - it.copy( - groups = groups - ) - } + _feedOptionUiState.update { it.copy(groups = groups) } } } } @@ -63,9 +60,7 @@ class FeedOptionViewModel @Inject constructor( } fun hideDrawer(scope: CoroutineScope) { - scope.launch { - _feedOptionUiState.value.drawerState.hide() - } + scope.launch { _feedOptionUiState.value.drawerState.hide() } } fun showNewGroupDialog() { @@ -87,11 +82,7 @@ class FeedOptionViewModel @Inject constructor( } fun inputNewGroup(content: String) { - _feedOptionUiState.update { - it.copy( - newGroupContent = content - ) - } + _feedOptionUiState.update { it.copy(newGroupContent = content) } } fun addNewGroup() { @@ -104,39 +95,27 @@ class FeedOptionViewModel @Inject constructor( } fun selectedGroup(groupId: String) { - viewModelScope.launch(dispatcherIO) { + viewModelScope.launch(ioDispatcher) { _feedOptionUiState.value.feed?.let { - rssRepository.get().updateFeed( - it.copy( - groupId = groupId - ) - ) + rssRepository.get().updateFeed(it.copy(groupId = groupId)) fetchFeed(it.id) } } } fun changeParseFullContentPreset() { - viewModelScope.launch(dispatcherIO) { + viewModelScope.launch(ioDispatcher) { _feedOptionUiState.value.feed?.let { - rssRepository.get().updateFeed( - it.copy( - isFullContent = !it.isFullContent - ) - ) + rssRepository.get().updateFeed(it.copy(isFullContent = !it.isFullContent)) fetchFeed(it.id) } } } fun changeAllowNotificationPreset() { - viewModelScope.launch(dispatcherIO) { + viewModelScope.launch(ioDispatcher) { _feedOptionUiState.value.feed?.let { - rssRepository.get().updateFeed( - it.copy( - isNotification = !it.isNotification - ) - ) + rssRepository.get().updateFeed(it.copy(isNotification = !it.isNotification)) fetchFeed(it.id) } } @@ -144,9 +123,9 @@ class FeedOptionViewModel @Inject constructor( fun delete(callback: () -> Unit = {}) { _feedOptionUiState.value.feed?.let { - viewModelScope.launch(dispatcherIO) { + viewModelScope.launch(ioDispatcher) { rssRepository.get().deleteFeed(it) - withContext(dispatcherMain) { + withContext(mainDispatcher) { callback() } } @@ -154,42 +133,26 @@ class FeedOptionViewModel @Inject constructor( } fun hideDeleteDialog() { - _feedOptionUiState.update { - it.copy( - deleteDialogVisible = false, - ) - } + _feedOptionUiState.update { it.copy(deleteDialogVisible = false) } } fun showDeleteDialog() { - _feedOptionUiState.update { - it.copy( - deleteDialogVisible = true, - ) - } + _feedOptionUiState.update { it.copy(deleteDialogVisible = true) } } fun showClearDialog() { - _feedOptionUiState.update { - it.copy( - clearDialogVisible = true, - ) - } + _feedOptionUiState.update { it.copy(clearDialogVisible = true) } } fun hideClearDialog() { - _feedOptionUiState.update { - it.copy( - clearDialogVisible = false, - ) - } + _feedOptionUiState.update { it.copy(clearDialogVisible = false) } } fun clearFeed(callback: () -> Unit = {}) { _feedOptionUiState.value.feed?.let { - viewModelScope.launch(dispatcherIO) { + viewModelScope.launch(ioDispatcher) { rssRepository.get().deleteArticles(feed = it) - withContext(dispatcherMain) { + withContext(mainDispatcher) { callback() } } @@ -199,16 +162,8 @@ class FeedOptionViewModel @Inject constructor( fun renameFeed() { _feedOptionUiState.value.feed?.let { viewModelScope.launch { - rssRepository.get().updateFeed( - it.copy( - name = _feedOptionUiState.value.newName - ) - ) - _feedOptionUiState.update { - it.copy( - renameDialogVisible = false, - ) - } + rssRepository.get().updateFeed(it.copy(name = _feedOptionUiState.value.newName)) + _feedOptionUiState.update { it.copy(renameDialogVisible = false) } } } } @@ -232,11 +187,7 @@ class FeedOptionViewModel @Inject constructor( } fun inputNewName(content: String) { - _feedOptionUiState.update { - it.copy( - newName = content - ) - } + _feedOptionUiState.update { it.copy(newName = content) } } fun showFeedUrlDialog() { @@ -258,26 +209,14 @@ class FeedOptionViewModel @Inject constructor( } fun inputNewUrl(content: String) { - _feedOptionUiState.update { - it.copy( - newUrl = content - ) - } + _feedOptionUiState.update { it.copy(newUrl = content) } } fun changeFeedUrl() { _feedOptionUiState.value.feed?.let { viewModelScope.launch { - rssRepository.get().updateFeed( - it.copy( - url = _feedOptionUiState.value.newUrl - ) - ) - _feedOptionUiState.update { - it.copy( - changeUrlDialogVisible = false, - ) - } + rssRepository.get().updateFeed(it.copy(url = _feedOptionUiState.value.newUrl)) + _feedOptionUiState.update { it.copy(changeUrlDialogVisible = false) } } } } diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionDrawer.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionDrawer.kt index b64d5aa..298532c 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionDrawer.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionDrawer.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Article -import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.Folder import androidx.compose.material.icons.outlined.Notifications import androidx.compose.material3.Icon @@ -32,11 +31,11 @@ import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.MainAxisAlignment import kotlinx.coroutines.launch import me.ash.reader.R -import me.ash.reader.data.entity.Group +import me.ash.reader.data.model.group.Group +import me.ash.reader.ui.component.RenameDialog import me.ash.reader.ui.component.base.BottomDrawer import me.ash.reader.ui.component.base.RYSelectionChip import me.ash.reader.ui.component.base.Subtitle -import me.ash.reader.ui.component.base.TextFieldDialog import me.ash.reader.ui.ext.* @OptIn(ExperimentalMaterialApi::class) @@ -130,12 +129,9 @@ fun GroupOptionDrawer( AllAllowNotificationDialog(groupName = group?.name ?: "") AllParseFullContentDialog(groupName = group?.name ?: "") AllMoveToGroupDialog(groupName = group?.name ?: "") - TextFieldDialog( + RenameDialog( visible = groupOptionUiState.renameDialogVisible, - title = stringResource(R.string.rename), - icon = Icons.Outlined.Edit, value = groupOptionUiState.newName, - placeholder = stringResource(R.string.name), onValueChange = { groupOptionViewModel.inputNewName(it) }, @@ -154,7 +150,7 @@ fun GroupOptionDrawer( private fun Preset( groupOptionViewModel: GroupOptionViewModel, group: Group?, - context: Context + context: Context, ) { FlowRow( mainAxisAlignment = MainAxisAlignment.Start, @@ -217,7 +213,7 @@ private fun Preset( private fun FlowRowGroups( groupOptionUiState: GroupOptionUiState, group: Group?, - groupOptionViewModel: GroupOptionViewModel + groupOptionViewModel: GroupOptionViewModel, ) { FlowRow( mainAxisAlignment = MainAxisAlignment.Start, @@ -243,7 +239,7 @@ private fun FlowRowGroups( private fun LazyRowGroups( groupOptionUiState: GroupOptionUiState, group: Group?, - groupOptionViewModel: GroupOptionViewModel + groupOptionViewModel: GroupOptionViewModel, ) { LazyRow { items(groupOptionUiState.groups) { diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionViewModel.kt index 1c2ee46..dae28da 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionViewModel.kt @@ -14,9 +14,9 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import me.ash.reader.data.entity.Group -import me.ash.reader.data.module.DispatcherIO -import me.ash.reader.data.module.DispatcherMain +import me.ash.reader.data.model.group.Group +import me.ash.reader.data.module.IODispatcher +import me.ash.reader.data.module.MainDispatcher import me.ash.reader.data.repository.RssRepository import javax.inject.Inject @@ -24,48 +24,39 @@ import javax.inject.Inject @HiltViewModel class GroupOptionViewModel @Inject constructor( private val rssRepository: RssRepository, - @DispatcherMain - private val dispatcherMain: CoroutineDispatcher, - @DispatcherIO - private val dispatcherIO: CoroutineDispatcher, + @MainDispatcher + private val mainDispatcher: CoroutineDispatcher, + @IODispatcher + private val ioDispatcher: CoroutineDispatcher, ) : ViewModel() { + private val _groupOptionUiState = MutableStateFlow(GroupOptionUiState()) val groupOptionUiState: StateFlow = _groupOptionUiState.asStateFlow() init { - viewModelScope.launch(dispatcherIO) { + viewModelScope.launch(ioDispatcher) { rssRepository.get().pullGroups().collect { groups -> - _groupOptionUiState.update { - it.copy( - groups = groups - ) - } + _groupOptionUiState.update { it.copy(groups = groups) } } } } fun showDrawer(scope: CoroutineScope, groupId: String) { scope.launch { - _groupOptionUiState.update { - it.copy( - group = rssRepository.get().findGroupById(groupId), - ) - } + _groupOptionUiState.update { it.copy(group = rssRepository.get().findGroupById(groupId)) } _groupOptionUiState.value.drawerState.show() } } fun hideDrawer(scope: CoroutineScope) { - scope.launch { - _groupOptionUiState.value.drawerState.hide() - } + scope.launch { _groupOptionUiState.value.drawerState.hide() } } fun allAllowNotification(isNotification: Boolean, callback: () -> Unit = {}) { _groupOptionUiState.value.group?.let { - viewModelScope.launch(dispatcherIO) { + viewModelScope.launch(ioDispatcher) { rssRepository.get().groupAllowNotification(it, isNotification) - withContext(dispatcherMain) { + withContext(mainDispatcher) { callback() } } @@ -73,26 +64,18 @@ class GroupOptionViewModel @Inject constructor( } fun showAllAllowNotificationDialog() { - _groupOptionUiState.update { - it.copy( - allAllowNotificationDialogVisible = true, - ) - } + _groupOptionUiState.update { it.copy(allAllowNotificationDialogVisible = true) } } fun hideAllAllowNotificationDialog() { - _groupOptionUiState.update { - it.copy( - allAllowNotificationDialogVisible = false, - ) - } + _groupOptionUiState.update { it.copy(allAllowNotificationDialogVisible = false) } } fun allParseFullContent(isFullContent: Boolean, callback: () -> Unit = {}) { _groupOptionUiState.value.group?.let { - viewModelScope.launch(dispatcherIO) { + viewModelScope.launch(ioDispatcher) { rssRepository.get().groupParseFullContent(it, isFullContent) - withContext(dispatcherMain) { + withContext(mainDispatcher) { callback() } } @@ -100,26 +83,18 @@ class GroupOptionViewModel @Inject constructor( } fun showAllParseFullContentDialog() { - _groupOptionUiState.update { - it.copy( - allParseFullContentDialogVisible = true, - ) - } + _groupOptionUiState.update { it.copy(allParseFullContentDialogVisible = true) } } fun hideAllParseFullContentDialog() { - _groupOptionUiState.update { - it.copy( - allParseFullContentDialogVisible = false, - ) - } + _groupOptionUiState.update { it.copy(allParseFullContentDialogVisible = false) } } fun delete(callback: () -> Unit = {}) { _groupOptionUiState.value.group?.let { - viewModelScope.launch(dispatcherIO) { + viewModelScope.launch(ioDispatcher) { rssRepository.get().deleteGroup(it) - withContext(dispatcherMain) { + withContext(mainDispatcher) { callback() } } @@ -127,42 +102,26 @@ class GroupOptionViewModel @Inject constructor( } fun showDeleteDialog() { - _groupOptionUiState.update { - it.copy( - deleteDialogVisible = true, - ) - } + _groupOptionUiState.update { it.copy(deleteDialogVisible = true) } } fun hideDeleteDialog() { - _groupOptionUiState.update { - it.copy( - deleteDialogVisible = false, - ) - } + _groupOptionUiState.update { it.copy(deleteDialogVisible = false) } } fun showClearDialog() { - _groupOptionUiState.update { - it.copy( - clearDialogVisible = true, - ) - } + _groupOptionUiState.update { it.copy(clearDialogVisible = true) } } fun hideClearDialog() { - _groupOptionUiState.update { - it.copy( - clearDialogVisible = false, - ) - } + _groupOptionUiState.update { it.copy(clearDialogVisible = false) } } fun clear(callback: () -> Unit = {}) { _groupOptionUiState.value.group?.let { - viewModelScope.launch(dispatcherIO) { + viewModelScope.launch(ioDispatcher) { rssRepository.get().deleteArticles(group = it) - withContext(dispatcherMain) { + withContext(mainDispatcher) { callback() } } @@ -172,9 +131,9 @@ class GroupOptionViewModel @Inject constructor( fun allMoveToGroup(callback: () -> Unit) { _groupOptionUiState.value.group?.let { group -> _groupOptionUiState.value.targetGroup?.let { targetGroup -> - viewModelScope.launch(dispatcherIO) { + viewModelScope.launch(ioDispatcher) { rssRepository.get().groupMoveToTargetGroup(group, targetGroup) - withContext(dispatcherMain) { + withContext(mainDispatcher) { callback() } } @@ -203,16 +162,8 @@ class GroupOptionViewModel @Inject constructor( fun rename() { _groupOptionUiState.value.group?.let { viewModelScope.launch { - rssRepository.get().updateGroup( - it.copy( - name = _groupOptionUiState.value.newName - ) - ) - _groupOptionUiState.update { - it.copy( - renameDialogVisible = false, - ) - } + rssRepository.get().updateGroup(it.copy(name = _groupOptionUiState.value.newName)) + _groupOptionUiState.update { it.copy(renameDialogVisible = false) } } } } @@ -236,11 +187,7 @@ class GroupOptionViewModel @Inject constructor( } fun inputNewName(content: String) { - _groupOptionUiState.update { - it.copy( - newName = content - ) - } + _groupOptionUiState.update { it.copy(newName = content) } } } diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeDialog.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeDialog.kt index 4e44430..0564224 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeDialog.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeDialog.kt @@ -29,6 +29,7 @@ import me.ash.reader.ui.component.base.ClipboardTextField import me.ash.reader.ui.component.base.RYDialog import me.ash.reader.ui.component.base.TextFieldDialog import me.ash.reader.ui.ext.collectAsStateValue +import me.ash.reader.ui.page.home.feeds.FeedOptionView @OptIn( androidx.compose.ui.ExperimentalComposeUiApi::class, @@ -36,7 +37,6 @@ import me.ash.reader.ui.ext.collectAsStateValue ) @Composable fun SubscribeDialog( - modifier: Modifier = Modifier, subscribeViewModel: SubscribeViewModel = hiltViewModel(), ) { val context = LocalContext.current @@ -109,7 +109,7 @@ fun SubscribeDialog( }, ) } else { - ResultView( + FeedOptionView( link = subscribeUiState.linkContent, groups = groupsState.value, selectedAllowNotificationPreset = subscribeUiState.allowNotificationPreset, @@ -201,4 +201,4 @@ fun SubscribeDialog( subscribeViewModel.addNewGroup() } ) -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt index a891ff4..6901719 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt @@ -8,9 +8,9 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import me.ash.reader.R -import me.ash.reader.data.entity.Article -import me.ash.reader.data.entity.Feed -import me.ash.reader.data.entity.Group +import me.ash.reader.data.model.article.Article +import me.ash.reader.data.model.feed.Feed +import me.ash.reader.data.model.group.Group import me.ash.reader.data.repository.OpmlRepository import me.ash.reader.data.repository.RssHelper import me.ash.reader.data.repository.RssRepository @@ -26,6 +26,7 @@ class SubscribeViewModel @Inject constructor( private val rssHelper: RssHelper, private val stringsRepository: StringsRepository, ) : ViewModel() { + private val _subscribeUiState = MutableStateFlow(SubscribeUiState()) val subscribeUiState: StateFlow = _subscribeUiState.asStateFlow() private var searchJob: Job? = null @@ -43,9 +44,7 @@ class SubscribeViewModel @Inject constructor( searchJob?.cancel() searchJob = null _subscribeUiState.update { - SubscribeUiState().copy( - title = stringsRepository.getString(R.string.subscribe), - ) + SubscribeUiState().copy(title = stringsRepository.getString(R.string.subscribe)) } } @@ -60,27 +59,8 @@ class SubscribeViewModel @Inject constructor( } } - fun subscribe() { - val feed = _subscribeUiState.value.feed ?: return - val articles = _subscribeUiState.value.articles - viewModelScope.launch { - rssRepository.get().subscribe( - feed.copy( - groupId = _subscribeUiState.value.selectedGroupId, - isNotification = _subscribeUiState.value.allowNotificationPreset, - isFullContent = _subscribeUiState.value.parseFullContentPreset, - ), articles - ) - hideDrawer() - } - } - fun selectedGroup(groupId: String) { - _subscribeUiState.update { - it.copy( - selectedGroupId = groupId, - ) - } + _subscribeUiState.update { it.copy(selectedGroupId = groupId) } } fun addNewGroup() { @@ -88,28 +68,20 @@ class SubscribeViewModel @Inject constructor( viewModelScope.launch { selectedGroup(rssRepository.get().addGroup(_subscribeUiState.value.newGroupContent)) hideNewGroupDialog() - _subscribeUiState.update { - it.copy( - newGroupContent = "", - ) - } + _subscribeUiState.update { it.copy(newGroupContent = "") } } } } fun changeParseFullContentPreset() { _subscribeUiState.update { - it.copy( - parseFullContentPreset = !_subscribeUiState.value.parseFullContentPreset - ) + it.copy(parseFullContentPreset = !_subscribeUiState.value.parseFullContentPreset) } } fun changeAllowNotificationPreset() { _subscribeUiState.update { - it.copy( - allowNotificationPreset = !_subscribeUiState.value.allowNotificationPreset - ) + it.copy(allowNotificationPreset = !_subscribeUiState.value.allowNotificationPreset) } } @@ -170,6 +142,21 @@ class SubscribeViewModel @Inject constructor( } } + fun subscribe() { + val feed = _subscribeUiState.value.feed ?: return + val articles = _subscribeUiState.value.articles + viewModelScope.launch { + rssRepository.get().subscribe( + feed.copy( + groupId = _subscribeUiState.value.selectedGroupId, + isNotification = _subscribeUiState.value.allowNotificationPreset, + isFullContent = _subscribeUiState.value.parseFullContentPreset, + ), articles + ) + hideDrawer() + } + } + fun inputLink(content: String) { _subscribeUiState.update { it.copy( @@ -180,51 +167,27 @@ class SubscribeViewModel @Inject constructor( } fun inputNewGroup(content: String) { - _subscribeUiState.update { - it.copy( - newGroupContent = content - ) - } + _subscribeUiState.update { it.copy(newGroupContent = content) } } fun showDrawer() { - _subscribeUiState.update { - it.copy( - visible = true - ) - } + _subscribeUiState.update { it.copy(visible = true) } } fun hideDrawer() { - _subscribeUiState.update { - it.copy( - visible = false - ) - } + _subscribeUiState.update { it.copy(visible = false) } } fun showNewGroupDialog() { - _subscribeUiState.update { - it.copy( - newGroupDialogVisible = true, - ) - } + _subscribeUiState.update { it.copy(newGroupDialogVisible = true) } } fun hideNewGroupDialog() { - _subscribeUiState.update { - it.copy( - newGroupDialogVisible = false, - ) - } + _subscribeUiState.update { it.copy(newGroupDialogVisible = false) } } fun switchPage(isSearchPage: Boolean) { - _subscribeUiState.update { - it.copy( - isSearchPage = isSearchPage - ) - } + _subscribeUiState.update { it.copy(isSearchPage = isSearchPage) } } } diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt index 012a939..8d8d285 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt @@ -19,8 +19,8 @@ import androidx.compose.ui.unit.dp import coil.size.Precision import coil.size.Scale import me.ash.reader.R -import me.ash.reader.data.entity.ArticleWithFeed -import me.ash.reader.data.preference.* +import me.ash.reader.data.model.article.ArticleWithFeed +import me.ash.reader.data.model.preference.* import me.ash.reader.ui.component.FeedIcon import me.ash.reader.ui.component.base.RYAsyncImage import me.ash.reader.ui.component.base.SIZE_1000 @@ -150,4 +150,4 @@ fun ArticleItem( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt index 6483ede..13665ce 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt @@ -7,12 +7,13 @@ import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.paging.compose.LazyPagingItems -import me.ash.reader.data.entity.ArticleWithFeed +import me.ash.reader.data.model.article.ArticleFlowItem +import me.ash.reader.data.model.article.ArticleWithFeed @Suppress("FunctionName") @OptIn(ExperimentalFoundationApi::class) fun LazyListScope.ArticleList( - pagingItems: LazyPagingItems, + pagingItems: LazyPagingItems, isShowFeedIcon: Boolean, isShowStickyHeader: Boolean, articleListTonalElevation: Int, @@ -20,16 +21,17 @@ fun LazyListScope.ArticleList( ) { for (index in 0 until pagingItems.itemCount) { when (val item = pagingItems.peek(index)) { - is FlowItemView.Article -> { + is ArticleFlowItem.Article -> { item(key = item.articleWithFeed.article.id) { ArticleItem( - articleWithFeed = (pagingItems[index] as FlowItemView.Article).articleWithFeed, + articleWithFeed = (pagingItems[index] as ArticleFlowItem.Article).articleWithFeed, ) { onClick(it) } } } - is FlowItemView.Date -> { + + is ArticleFlowItem.Date -> { if (item.showSpacer) item { Spacer(modifier = Modifier.height(40.dp)) } if (isShowStickyHeader) { stickyHeader(key = item.date) { @@ -41,7 +43,8 @@ fun LazyListScope.ArticleList( } } } + else -> {} } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt index 231dbed..850d086 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt @@ -23,8 +23,7 @@ import androidx.paging.compose.collectAsLazyPagingItems import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.ash.reader.R -import me.ash.reader.data.model.getName -import me.ash.reader.data.preference.* +import me.ash.reader.data.model.preference.* import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing import me.ash.reader.ui.component.FilterBar import me.ash.reader.ui.component.base.DisplayText @@ -169,7 +168,7 @@ fun FlowPage( text = when { filterUiState.group != null -> filterUiState.group.name filterUiState.feed != null -> filterUiState.feed.name - else -> filterUiState.filter.getName() + else -> filterUiState.filter.toName() }, desc = if (isSyncing) stringResource(R.string.syncing) else "", ) @@ -188,7 +187,7 @@ fun FlowPage( groupId = filterUiState.group?.id, feedId = filterUiState.feed?.id, articleId = null, - markAsReadBefore = it, + conditions = it, ) } RYExtensibleVisibility(visible = onSearch) { @@ -197,17 +196,19 @@ fun FlowPage( placeholder = when { filterUiState.group != null -> stringResource( R.string.search_for_in, - filterUiState.filter.getName(), + filterUiState.filter.toName(), filterUiState.group.name ) + filterUiState.feed != null -> stringResource( R.string.search_for_in, - filterUiState.filter.getName(), + filterUiState.filter.toName(), filterUiState.feed.name ) + else -> stringResource( R.string.search_for, - filterUiState.filter.getName() + filterUiState.filter.toName() ) }, focusRequester = focusRequester, diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt index 392045f..12e54d2 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt @@ -8,15 +8,15 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -import me.ash.reader.data.entity.ArticleWithFeed +import me.ash.reader.data.model.general.MarkAsReadConditions import me.ash.reader.data.repository.RssRepository -import java.util.* import javax.inject.Inject @HiltViewModel class FlowViewModel @Inject constructor( private val rssRepository: RssRepository, ) : ViewModel() { + private val _flowUiState = MutableStateFlow(FlowUiState()) val flowUiState: StateFlow = _flowUiState.asStateFlow() @@ -34,28 +34,14 @@ class FlowViewModel @Inject constructor( groupId: String?, feedId: String?, articleId: String?, - markAsReadBefore: MarkAsReadBefore + conditions: MarkAsReadConditions, ) { viewModelScope.launch { rssRepository.get().markAsRead( groupId = groupId, feedId = feedId, articleId = articleId, - before = when (markAsReadBefore) { - MarkAsReadBefore.All -> null - MarkAsReadBefore.OneDay -> Calendar.getInstance().apply { - time = Date() - add(Calendar.DAY_OF_MONTH, -1) - }.time - MarkAsReadBefore.ThreeDays -> Calendar.getInstance().apply { - time = Date() - add(Calendar.DAY_OF_MONTH, -3) - }.time - MarkAsReadBefore.SevenDays -> Calendar.getInstance().apply { - time = Date() - add(Calendar.DAY_OF_MONTH, -7) - }.time - }, + before = conditions.toDate(), isUnread = false, ) } @@ -68,15 +54,3 @@ data class FlowUiState( val isBack: Boolean = false, val syncWorkInfo: String = "", ) - -enum class MarkAsReadBefore { - SevenDays, - ThreeDays, - OneDay, - All, -} - -sealed class FlowItemView { - class Article(val articleWithFeed: ArticleWithFeed) : FlowItemView() - class Date(val date: String, val showSpacer: Boolean) : FlowItemView() -} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/MarkAsReadBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/MarkAsReadBar.kt index 5393f19..d8e6814 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/MarkAsReadBar.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/MarkAsReadBar.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import me.ash.reader.R +import me.ash.reader.data.model.general.MarkAsReadConditions import me.ash.reader.ui.component.base.AnimatedPopup import me.ash.reader.ui.theme.palette.alwaysLight @@ -31,7 +32,7 @@ fun MarkAsReadBar( visible: Boolean = false, absoluteY: Dp = Dp.Hairline, onDismissRequest: () -> Unit = {}, - onItemClick: (MarkAsReadBefore) -> Unit = {}, + onItemClick: (MarkAsReadConditions) -> Unit = {}, ) { val animated = remember { Animatable(absoluteY.value) } @@ -56,26 +57,26 @@ fun MarkAsReadBar( modifier = Modifier.width(56.dp), text = stringResource(R.string.seven_days), ) { - onItemClick(MarkAsReadBefore.SevenDays) + onItemClick(MarkAsReadConditions.SevenDays) } MarkAsReadBarItem( modifier = Modifier.width(56.dp), text = stringResource(R.string.three_days), ) { - onItemClick(MarkAsReadBefore.ThreeDays) + onItemClick(MarkAsReadConditions.ThreeDays) } MarkAsReadBarItem( modifier = Modifier.width(56.dp), text = stringResource(R.string.one_day), ) { - onItemClick(MarkAsReadBefore.OneDay) + onItemClick(MarkAsReadConditions.OneDay) } MarkAsReadBarItem( modifier = Modifier.weight(1f), text = stringResource(R.string.mark_all_as_read), isPrimary = true, ) { - onItemClick(MarkAsReadBefore.All) + onItemClick(MarkAsReadConditions.All) } } } diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingViewModel.kt index 8095268..8f250ec 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingViewModel.kt @@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import me.ash.reader.data.entity.ArticleWithFeed +import me.ash.reader.data.model.article.ArticleWithFeed import me.ash.reader.data.repository.RssHelper import me.ash.reader.data.repository.RssRepository import javax.inject.Inject @@ -21,6 +21,7 @@ class ReadingViewModel @Inject constructor( private val rssRepository: RssRepository, private val rssHelper: RssHelper, ) : ViewModel() { + private val _readingUiState = MutableStateFlow(ReadingUiState()) val readingUiState: StateFlow = _readingUiState.asStateFlow() @@ -28,9 +29,7 @@ class ReadingViewModel @Inject constructor( showLoading() viewModelScope.launch { _readingUiState.update { - it.copy( - articleWithFeed = rssRepository.get().findArticleById(articleId) - ) + it.copy(articleWithFeed = rssRepository.get().findArticleById(articleId)) } _readingUiState.value.articleWithFeed?.let { if (it.feed.isFullContent) internalRenderFullContent() @@ -70,11 +69,7 @@ class ReadingViewModel @Inject constructor( } } catch (e: Exception) { Log.i("RLog", "renderFullContent: ${e.message}") - _readingUiState.update { - it.copy( - content = e.message - ) - } + _readingUiState.update { it.copy(content = e.message) } } hideLoading() } @@ -114,9 +109,7 @@ class ReadingViewModel @Inject constructor( ) } rssRepository.get().updateArticleInfo( - articleWithFeed.article.copy( - isStarred = isStarred - ) + articleWithFeed.article.copy(isStarred = isStarred) ) } } @@ -140,4 +133,4 @@ data class ReadingUiState( val isFullContent: Boolean = false, val isLoading: Boolean = true, val listState: LazyListState = LazyListState(), -) \ No newline at end of file +) diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/SettingItem.kt b/app/src/main/java/me/ash/reader/ui/page/settings/SettingItem.kt index 8cd111e..03d5209 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/SettingItem.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/SettingItem.kt @@ -31,7 +31,7 @@ fun SettingItem( icon: ImageVector? = null, separatedActions: Boolean = false, onClick: () -> Unit, - action: (@Composable () -> Unit)? = null + action: (@Composable () -> Unit)? = null, ) { val tonalPalettes = LocalTonalPalettes.current 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 6821916..5f5ab3d 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 @@ -20,8 +20,8 @@ import androidx.compose.ui.zIndex import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import me.ash.reader.R -import me.ash.reader.data.preference.LocalNewVersionNumber -import me.ash.reader.data.preference.LocalSkipVersionNumber +import me.ash.reader.data.model.preference.LocalNewVersionNumber +import me.ash.reader.data.model.preference.LocalSkipVersionNumber import me.ash.reader.ui.component.base.Banner import me.ash.reader.ui.component.base.DisplayText import me.ash.reader.ui.component.base.FeedbackIconButton diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/ColorAndStylePage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/ColorAndStylePage.kt index 9f5b697..bede78c 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/color/ColorAndStylePage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/ColorAndStylePage.kt @@ -15,7 +15,10 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Check import androidx.compose.material.icons.outlined.Palette import androidx.compose.material.icons.rounded.ArrowBack -import androidx.compose.material3.* +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -25,7 +28,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import me.ash.reader.R -import me.ash.reader.data.preference.* +import me.ash.reader.data.model.preference.* import me.ash.reader.ui.component.base.* import me.ash.reader.ui.page.common.RouteName import me.ash.reader.ui.page.settings.SettingItem @@ -35,7 +38,6 @@ import me.ash.reader.ui.theme.palette.* import me.ash.reader.ui.theme.palette.TonalPalettes.Companion.toTonalPalettes import me.ash.reader.ui.theme.palette.dynamic.extractTonalPalettesFromUserWallpaper -@OptIn(ExperimentalMaterial3Api::class) @Composable fun ColorAndStylePage( navController: NavHostController, @@ -134,7 +136,7 @@ fun ColorAndStylePage( ) SettingItem( title = stringResource(R.string.dark_theme), - desc = darkTheme.getDesc(context), + desc = darkTheme.toDesc(context), separatedActions = true, onClick = { navController.navigate(RouteName.DARK_THEME) { @@ -193,7 +195,6 @@ fun ColorAndStylePage( @Composable fun Palettes( - modifier: Modifier = Modifier, context: Context, palettes: List, themeIndex: Int = 0, @@ -338,4 +339,4 @@ fun SelectableMiniPalette( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/DarkThemePage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/DarkThemePage.kt index be16636..c88c07e 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/color/DarkThemePage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/DarkThemePage.kt @@ -15,10 +15,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import me.ash.reader.R -import me.ash.reader.data.preference.DarkThemePreference -import me.ash.reader.data.preference.LocalAmoledDarkTheme -import me.ash.reader.data.preference.LocalDarkTheme -import me.ash.reader.data.preference.not +import me.ash.reader.data.model.preference.DarkThemePreference +import me.ash.reader.data.model.preference.LocalAmoledDarkTheme +import me.ash.reader.data.model.preference.LocalDarkTheme +import me.ash.reader.data.model.preference.not import me.ash.reader.ui.component.base.* import me.ash.reader.ui.page.settings.SettingItem import me.ash.reader.ui.theme.palette.onLight @@ -52,7 +52,7 @@ fun DarkThemePage( item { DarkThemePreference.values.map { SettingItem( - title = it.getDesc(context), + title = it.toDesc(context), onClick = { it.put(context, scope) }, diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPagePreview.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPagePreview.kt index f5e945c..704c6b0 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPagePreview.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPagePreview.kt @@ -19,12 +19,12 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import me.ash.reader.R -import me.ash.reader.data.entity.Feed -import me.ash.reader.data.entity.Group -import me.ash.reader.data.model.Filter -import me.ash.reader.data.preference.FeedsGroupListExpandPreference -import me.ash.reader.data.preference.FeedsGroupListTonalElevationPreference -import me.ash.reader.data.preference.FeedsTopBarTonalElevationPreference +import me.ash.reader.data.model.feed.Feed +import me.ash.reader.data.model.general.Filter +import me.ash.reader.data.model.group.Group +import me.ash.reader.data.model.preference.FeedsGroupListExpandPreference +import me.ash.reader.data.model.preference.FeedsGroupListTonalElevationPreference +import me.ash.reader.data.model.preference.FeedsTopBarTonalElevationPreference import me.ash.reader.ui.component.FilterBar import me.ash.reader.ui.component.base.FeedbackIconButton import me.ash.reader.ui.ext.alphaLN @@ -141,4 +141,4 @@ fun generateGroupPreview(): Group = id = "", name = stringResource(R.string.defaults), accountId = 0, - ) \ No newline at end of file + ) diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPageStylePage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPageStylePage.kt index 3a1a953..b0ec6cb 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPageStylePage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPageStylePage.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import me.ash.reader.R -import me.ash.reader.data.preference.* +import me.ash.reader.data.model.preference.* import me.ash.reader.ui.component.base.* import me.ash.reader.ui.page.settings.SettingItem import me.ash.reader.ui.theme.palette.onLight @@ -142,7 +142,7 @@ fun FeedsPageStylePage( ) SettingItem( title = stringResource(R.string.style), - desc = filterBarStyle.getDesc(context), + desc = filterBarStyle.toDesc(context), onClick = { filterBarStyleDialogVisible = true }, @@ -185,7 +185,7 @@ fun FeedsPageStylePage( title = stringResource(R.string.style), options = FeedsFilterBarStylePreference.values.map { RadioDialogOption( - text = it.getDesc(context), + text = it.toDesc(context), selected = filterBarStyle == it, ) { it.put(context, scope) @@ -217,7 +217,7 @@ fun FeedsPageStylePage( title = stringResource(R.string.tonal_elevation), options = FeedsFilterBarTonalElevationPreference.values.map { RadioDialogOption( - text = it.getDesc(context), + text = it.toDesc(context), selected = it == filterBarTonalElevation, ) { it.put(context, scope) @@ -232,7 +232,7 @@ fun FeedsPageStylePage( title = stringResource(R.string.tonal_elevation), options = FeedsTopBarTonalElevationPreference.values.map { RadioDialogOption( - text = it.getDesc(context), + text = it.toDesc(context), selected = it == topBarTonalElevation, ) { it.put(context, scope) @@ -247,7 +247,7 @@ fun FeedsPageStylePage( title = stringResource(R.string.tonal_elevation), options = FeedsGroupListTonalElevationPreference.values.map { RadioDialogOption( - text = it.getDesc(context), + text = it.toDesc(context), selected = it == groupListTonalElevation, ) { it.put(context, scope) diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPagePreview.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPagePreview.kt index d3101fb..027babf 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPagePreview.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPagePreview.kt @@ -19,12 +19,12 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import me.ash.reader.R -import me.ash.reader.data.entity.Article -import me.ash.reader.data.entity.ArticleWithFeed -import me.ash.reader.data.entity.Feed -import me.ash.reader.data.model.Filter -import me.ash.reader.data.preference.FlowArticleListTonalElevationPreference -import me.ash.reader.data.preference.FlowTopBarTonalElevationPreference +import me.ash.reader.data.model.article.Article +import me.ash.reader.data.model.article.ArticleWithFeed +import me.ash.reader.data.model.feed.Feed +import me.ash.reader.data.model.general.Filter +import me.ash.reader.data.model.preference.FlowArticleListTonalElevationPreference +import me.ash.reader.data.model.preference.FlowTopBarTonalElevationPreference import me.ash.reader.ui.component.FilterBar import me.ash.reader.ui.component.base.FeedbackIconButton import me.ash.reader.ui.ext.surfaceColorAtElevation diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPageStylePage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPageStylePage.kt index add9a0d..434f921 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPageStylePage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPageStylePage.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import me.ash.reader.R -import me.ash.reader.data.preference.* +import me.ash.reader.data.model.preference.* import me.ash.reader.ui.component.base.* import me.ash.reader.ui.page.settings.SettingItem import me.ash.reader.ui.theme.palette.onLight @@ -202,7 +202,7 @@ fun FlowPageStylePage( ) SettingItem( title = stringResource(R.string.style), - desc = filterBarStyle.getDesc(context), + desc = filterBarStyle.toDesc(context), onClick = { filterBarStyleDialogVisible = true }, @@ -245,7 +245,7 @@ fun FlowPageStylePage( title = stringResource(R.string.style), options = FlowFilterBarStylePreference.values.map { RadioDialogOption( - text = it.getDesc(context), + text = it.toDesc(context), selected = it == filterBarStyle, ) { it.put(context, scope) @@ -277,7 +277,7 @@ fun FlowPageStylePage( title = stringResource(R.string.tonal_elevation), options = FlowFilterBarTonalElevationPreference.values.map { RadioDialogOption( - text = it.getDesc(context), + text = it.toDesc(context), selected = it == filterBarTonalElevation, ) { it.put(context, scope) @@ -292,7 +292,7 @@ fun FlowPageStylePage( title = stringResource(R.string.tonal_elevation), options = FlowTopBarTonalElevationPreference.values.map { RadioDialogOption( - text = it.getDesc(context), + text = it.toDesc(context), selected = it == topBarTonalElevation, ) { it.put(context, scope) @@ -307,7 +307,7 @@ fun FlowPageStylePage( title = stringResource(R.string.tonal_elevation), options = FlowArticleListTonalElevationPreference.values.map { RadioDialogOption( - text = it.getDesc(context), + text = it.toDesc(context), selected = it == articleListTonalElevation, ) { it.put(context, scope) diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/interaction/InteractionPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/interaction/InteractionPage.kt index e8b39cc..df6f89f 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/interaction/InteractionPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/interaction/InteractionPage.kt @@ -12,10 +12,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import me.ash.reader.R -import me.ash.reader.data.preference.InitialFilterPreference -import me.ash.reader.data.preference.InitialPagePreference -import me.ash.reader.data.preference.LocalInitialFilter -import me.ash.reader.data.preference.LocalInitialPage +import me.ash.reader.data.model.preference.InitialFilterPreference +import me.ash.reader.data.model.preference.InitialPagePreference +import me.ash.reader.data.model.preference.LocalInitialFilter +import me.ash.reader.data.model.preference.LocalInitialPage import me.ash.reader.ui.component.base.* import me.ash.reader.ui.page.settings.SettingItem import me.ash.reader.ui.theme.palette.onLight @@ -55,14 +55,14 @@ fun InteractionPage( ) SettingItem( title = stringResource(R.string.initial_page), - desc = initialPage.getDesc(context), + desc = initialPage.toDesc(context), onClick = { initialPageDialogVisible = true }, ) {} SettingItem( title = stringResource(R.string.initial_filter), - desc = initialFilter.getDesc(context), + desc = initialFilter.toDesc(context), onClick = { initialFilterDialogVisible = true }, @@ -80,7 +80,7 @@ fun InteractionPage( title = stringResource(R.string.initial_page), options = InitialPagePreference.values.map { RadioDialogOption( - text = it.getDesc(context), + text = it.toDesc(context), selected = it == initialPage, ) { it.put(context, scope) @@ -95,7 +95,7 @@ fun InteractionPage( title = stringResource(R.string.initial_filter), options = InitialFilterPreference.values.map { RadioDialogOption( - text = it.getDesc(context), + text = it.toDesc(context), selected = it == initialFilter, ) { it.put(context, scope) diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/languages/LanguagesPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/languages/LanguagesPage.kt index 0268562..e71a3e1 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/languages/LanguagesPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/languages/LanguagesPage.kt @@ -20,8 +20,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import me.ash.reader.R -import me.ash.reader.data.preference.LanguagesPreference -import me.ash.reader.data.preference.LocalLanguages +import me.ash.reader.data.model.preference.LanguagesPreference +import me.ash.reader.data.model.preference.LocalLanguages import me.ash.reader.ui.component.base.Banner import me.ash.reader.ui.component.base.DisplayText import me.ash.reader.ui.component.base.FeedbackIconButton @@ -76,7 +76,7 @@ fun LanguagesPage( item { LanguagesPreference.values.map { SettingItem( - title = it.getDesc(context), + title = it.toDesc(context), onClick = { it.put(context, scope) }, diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/tips/TipsAndSupportPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/tips/TipsAndSupportPage.kt index c2d7b9a..7fdaf08 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/tips/TipsAndSupportPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/tips/TipsAndSupportPage.kt @@ -246,6 +246,7 @@ sealed class RoundIconButtonType( open val backgroundColor: Color = Color.Unspecified, open val onClick: () -> Unit = {}, ) { + @Immutable data class Sponsor( val desc: Int = R.string.sponsor, @@ -317,6 +318,7 @@ private fun RoundIconButton(type: RoundIconButtonType) { tint = MaterialTheme.colorScheme.onSurface alwaysLight true, ) } + is RoundIconButtonType.GitHub, is RoundIconButtonType.Telegram -> { Icon( modifier = type.offset.size(type.size), @@ -327,4 +329,4 @@ private fun RoundIconButton(type: RoundIconButtonType) { } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateDialog.kt b/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateDialog.kt index 5bce517..d65d840 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateDialog.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateDialog.kt @@ -31,7 +31,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import kotlinx.coroutines.Dispatchers import me.ash.reader.R -import me.ash.reader.data.preference.* +import me.ash.reader.data.model.preference.* import me.ash.reader.data.source.Download import me.ash.reader.ui.component.base.RYDialog import me.ash.reader.ui.ext.collectAsStateValue @@ -155,4 +155,4 @@ fun UpdateDialog( } }, ) -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateViewModel.kt index 502759e..efc5030 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateViewModel.kt @@ -14,12 +14,13 @@ import javax.inject.Inject class UpdateViewModel @Inject constructor( private val ryRepository: RYRepository, ) : ViewModel() { + private val _updateUiState = MutableStateFlow(UpdateUiState()) val updateUiState: StateFlow = _updateUiState.asStateFlow() fun checkUpdate( preProcessor: suspend () -> Unit = {}, - postProcessor: suspend (Boolean) -> Unit = {} + postProcessor: suspend (Boolean) -> Unit = {}, ) { if (notFdroid) { viewModelScope.launch { diff --git a/app/src/main/java/me/ash/reader/ui/theme/Theme.kt b/app/src/main/java/me/ash/reader/ui/theme/Theme.kt index 0696c1a..da6e548 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/Theme.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/Theme.kt @@ -3,7 +3,7 @@ package me.ash.reader.ui.theme import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import me.ash.reader.data.preference.LocalThemeIndex +import me.ash.reader.data.model.preference.LocalThemeIndex import me.ash.reader.ui.theme.palette.LocalTonalPalettes import me.ash.reader.ui.theme.palette.TonalPalettes import me.ash.reader.ui.theme.palette.core.ProvideZcamViewingConditions @@ -15,7 +15,7 @@ import me.ash.reader.ui.theme.palette.dynamicLightColorScheme fun AppTheme( useDarkTheme: Boolean, wallpaperPalettes: List = extractTonalPalettesFromUserWallpaper(), - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { val themeIndex = LocalThemeIndex.current @@ -45,4 +45,4 @@ fun AppTheme( ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/DynamicTonalPalette.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/DynamicTonalPalette.kt index a266317..3dbc416 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/DynamicTonalPalette.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/DynamicTonalPalette.kt @@ -7,8 +7,8 @@ import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.ui.graphics.Color -import me.ash.reader.data.preference.LocalAmoledDarkTheme -import me.ash.reader.data.preference.LocalDarkTheme +import me.ash.reader.data.model.preference.LocalAmoledDarkTheme +import me.ash.reader.data.model.preference.LocalDarkTheme @Composable fun dynamicLightColorScheme(): ColorScheme { diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/MaterialYouStandard.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/MaterialYouStandard.kt index 47dc9d2..e331195 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/MaterialYouStandard.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/MaterialYouStandard.kt @@ -8,6 +8,7 @@ package me.ash.reader.ui.theme.palette object MaterialYouStandard { + // TODO: support RGB color spaces // calculated with error = 0.001, chroma = 100, hue in 0 until 360, hue step = 1 val sRGBLightnessChromaMap = mapOf( diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/TonalPalettes.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/TonalPalettes.kt index 23fe079..8c4854f 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/TonalPalettes.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/TonalPalettes.kt @@ -49,6 +49,7 @@ data class TonalPalettes( val neutralVariant: TonalPalette = mutableMapOf(), val error: TonalPalette = mutableMapOf(), ) { + @Composable infix fun primary(tone: TonalValue): Color = primary.getOrPut(tone) { zcamLch( @@ -205,6 +206,7 @@ data class TonalPalettes( } companion object { + @Composable @Stable fun Color.toTonalPalettes(): TonalPalettes { @@ -242,8 +244,9 @@ data class TonalPalettes( @RequiresApi(23) object ColorResourceHelper { + @DoNotInline fun getColor(context: Context, @ColorRes id: Int): Color { return Color(context.resources.getColor(id, context.theme)) } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/cielab/CieLab.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/cielab/CieLab.kt index d5821cd..a893d9c 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/cielab/CieLab.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/cielab/CieLab.kt @@ -16,9 +16,10 @@ data class CieLab( val a: Double, val b: Double, ) { + fun toXyz( whitePoint: CieXyz, - luminance: Double + luminance: Double, ): CieXyz { val lp = (L + 16.0) / 116.0 val absoluteWhitePoint = whitePoint * luminance @@ -30,6 +31,7 @@ data class CieLab( } companion object { + private fun f(x: Double) = when { x > 216.0 / 24389.0 -> x.pow(1.0 / 3.0) else -> x / (108.0 / 841.0) + 4.0 / 29.0 @@ -42,7 +44,7 @@ data class CieLab( fun CieXyz.toCieLab( whitePoint: CieXyz, - luminance: Double + luminance: Double, ): CieLab { val relativeWhitePoint = whitePoint / luminance return CieLab( diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/cielab/CieLch.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/cielab/CieLch.kt index 47ef2c9..f3a56a9 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/cielab/CieLch.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/cielab/CieLch.kt @@ -20,6 +20,7 @@ data class CieLch( val C: Double, val h: Double, ) { + fun toCieLab(): CieLab { val hRad = h.toRadians() return CieLab( @@ -30,6 +31,7 @@ data class CieLch( } companion object { + fun CieLab.toCieLch(): CieLch = CieLch( L = L, C = sqrt(square(a) + square(b)), diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/ciexyz/CieXyz.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/ciexyz/CieXyz.kt index 05bac71..71c227a 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/ciexyz/CieXyz.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/ciexyz/CieXyz.kt @@ -15,6 +15,7 @@ data class CieXyz( val y: Double, val z: Double, ) { + inline val xyz: DoubleArray get() = doubleArrayOf(x, y, z) @@ -26,6 +27,7 @@ data class CieXyz( operator fun div(luminance: Double): CieXyz = (xyz / luminance).asXyz() companion object { + internal fun DoubleArray.asXyz(): CieXyz = CieXyz(this[0], this[1], this[2]) } } diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/jzazbz/Jzazbz.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/jzazbz/Jzazbz.kt index e3d1220..ea5d9b6 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/jzazbz/Jzazbz.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/jzazbz/Jzazbz.kt @@ -16,6 +16,7 @@ data class Jzazbz( val az: Double, val bz: Double, ) { + fun toXyz(): CieXyz { val (x_, y_, z) = lmsToXyz * ( IzazbzToLms * doubleArrayOf( @@ -36,6 +37,7 @@ data class Jzazbz( } companion object { + private const val b = 1.15 private const val g = 0.66 private const val c_1 = 3424.0 / 4096.0 diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/jzazbz/Jzczhz.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/jzazbz/Jzczhz.kt index e4d1bc2..14567e8 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/jzazbz/Jzczhz.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/jzazbz/Jzczhz.kt @@ -20,6 +20,7 @@ data class Jzczhz( val Cz: Double, val hz: Double, ) { + fun toJzazbz(): Jzazbz { val hRad = hz.toRadians() return Jzazbz( @@ -33,6 +34,7 @@ data class Jzczhz( sqrt(square(Jz - other.Jz) + square(Cz - other.Cz) + 4.0 * Cz * other.Cz * square(sin((hz - other.hz) / 2.0))) companion object { + fun Jzazbz.toJzczhz(): Jzczhz = Jzczhz( Jz = Jz, Cz = sqrt(square(az) + square(bz)), diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/oklab/Oklab.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/oklab/Oklab.kt index 9d852fe..6d6e42b 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/oklab/Oklab.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/oklab/Oklab.kt @@ -17,10 +17,12 @@ data class Oklab( val a: Double, val b: Double, ) { + fun toXyz(): CieXyz = (lmsToXyz * (oklabToLms * doubleArrayOf(L, a, b)).map { it.pow(3.0) } .toDoubleArray()).asXyz() companion object { + private val xyzToLms: Matrix3 = Matrix3( doubleArrayOf(0.8189330101, 0.3618667424, -0.1288597137), doubleArrayOf(0.0329845436, 0.9293118715, 0.0361456387), diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/oklab/Oklch.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/oklab/Oklch.kt index 68a0027..bdede6f 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/oklab/Oklch.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/oklab/Oklch.kt @@ -23,6 +23,7 @@ data class Oklch( val C: Double, val h: Double, ) { + fun toOklab(): Oklab { val hRad = h.toRadians() return Oklab( @@ -42,7 +43,7 @@ data class Oklch( private fun findChromaBoundaryInRgb( colorSpace: RgbColorSpace, - error: Double + error: Double, ): Double = chromaBoundary.getOrPut(Triple(colorSpace.hashCode(), h, L)) { var low = 0.0 var high = C @@ -65,6 +66,7 @@ data class Oklch( } companion object { + fun Oklab.toOklch(): Oklch = Oklch( L = L, C = sqrt(square(a) + square(b)), diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/Rgb.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/Rgb.kt index d816f90..5d660e8 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/Rgb.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/Rgb.kt @@ -17,6 +17,7 @@ data class Rgb( val b: Double, val colorSpace: RgbColorSpace, ) { + inline val rgb: DoubleArray get() = doubleArrayOf(r, g, b) @@ -34,9 +35,10 @@ data class Rgb( override fun toString(): String = "Rgb(r=$r, g=$g, b=$b, colorSpace=${colorSpace.name})" companion object { + fun CieXyz.toRgb( luminance: Double, - colorSpace: RgbColorSpace + colorSpace: RgbColorSpace, ): Rgb = (colorSpace.rgbToXyzMatrix.inverse() * (xyz / luminance)) .map { colorSpace.transferFunction.OETF(it) } .toDoubleArray().asRgb(colorSpace) diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/RgbColorSpace.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/RgbColorSpace.kt index 7b6edde..e28419c 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/RgbColorSpace.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/RgbColorSpace.kt @@ -22,6 +22,7 @@ data class RgbColorSpace( internal val primaries: Matrix3, internal val transferFunction: TransferFunction, ) { + internal val rgbToXyzMatrix: Matrix3 get() { val M1 = Matrix3( @@ -46,6 +47,7 @@ data class RgbColorSpace( } companion object { + /** * Standard: IEC 61966-2-1 * - [Wikipedia: sRGB](https://en.wikipedia.org/wiki/SRGB) diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/GammaTransferFunction.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/GammaTransferFunction.kt index 61c3d4b..c0e7ab4 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/GammaTransferFunction.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/GammaTransferFunction.kt @@ -15,6 +15,7 @@ class GammaTransferFunction( val beta: Double, // linear-domain threshold β = K_0 / φ = E_t val delta: Double, // linear gain δ = φ ) : TransferFunction { + override fun EOTF(x: Double): Double = when { x >= beta * delta -> ((x + alpha - 1.0) / alpha).pow(gamma) // transition point βδ = K_0 else -> x / delta @@ -26,6 +27,7 @@ class GammaTransferFunction( } companion object { + /** * [Wikipedia: sRGB - Computing the transfer function](https://en.wikipedia.org/wiki/SRGB#Computing_the_transfer_function) */ diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/HLGTransferFunction.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/HLGTransferFunction.kt index 57b6257..1eca85d 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/HLGTransferFunction.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/HLGTransferFunction.kt @@ -16,7 +16,9 @@ import kotlin.math.sqrt * [Rec. 2100](https://www.itu.int/rec/R-REC-BT.2100) */ class HLGTransferFunction : TransferFunction { + companion object { + private val a = 0.17883277 private val b = 1.0 - 4.0 * a // 0.28466892 private val c = 0.5 - a * ln(4.0 * a) // 0.55991073 diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/PQTransferFunction.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/PQTransferFunction.kt index 408ec20..51e5315 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/PQTransferFunction.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/PQTransferFunction.kt @@ -13,7 +13,9 @@ import kotlin.math.pow * [Rec. 2100](https://www.itu.int/rec/R-REC-BT.2100) */ class PQTransferFunction : TransferFunction { + companion object { + private val m_1 = 2610.0 / 16384.0 // 0.1593017578125 private val m_2 = 2523.0 / 4096.0 * 128.0 // 78.84375 private val c_1 = 3424.0 / 4096.0 // 0.8359375 = c_3 − c_2 + 1 diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/TransferFunction.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/TransferFunction.kt index ba70d64..aa83fa9 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/TransferFunction.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/rgb/transferfunction/TransferFunction.kt @@ -8,6 +8,7 @@ package me.ash.reader.ui.theme.palette.colorspace.rgb.transferfunction interface TransferFunction { + // nonlinear -> linear fun EOTF(x: Double): Double diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/zcam/Izazbz.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/zcam/Izazbz.kt index 00a8609..fa9b405 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/zcam/Izazbz.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/zcam/Izazbz.kt @@ -16,6 +16,7 @@ data class Izazbz( val az: Double, val bz: Double, ) { + fun toXyz(): CieXyz { val (x_, y_, z) = lmsToXyz * (IzazbzToLms * doubleArrayOf(Iz + epsilon, az, bz)).map { 10000.0 * ((c_1 - it.pow(1.0 / rho)) / (c_3 * it.pow(1.0 / rho) - c_2)).pow(1.0 / eta) @@ -30,6 +31,7 @@ data class Izazbz( } companion object { + private const val b = 1.15 private const val g = 0.66 private const val c_1 = 3424.0 / 4096.0 diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/zcam/Zcam.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/zcam/Zcam.kt index 77e875c..9955d88 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/zcam/Zcam.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/colorspace/zcam/Zcam.kt @@ -29,6 +29,7 @@ data class Zcam( val Wz: Double = Double.NaN, val cond: ViewingConditions, ) { + fun toIzazbz(): Izazbz { require(!hz.isNaN()) { "Must provide hz." } require(!Qz.isNaN() || !Jz.isNaN()) { "Must provide Qz or Jz." } @@ -87,7 +88,7 @@ data class Zcam( private fun findChromaBoundaryInRgb( colorSpace: RgbColorSpace, - error: Double + error: Double, ): Double = chromaBoundary.getOrPut(Triple(colorSpace.hashCode(), hz, Jz)) { var low = 0.0 var high = Cz @@ -111,6 +112,7 @@ data class Zcam( } companion object { + private val chromaBoundary: MutableMap, Double> = mutableMapOf() data class ViewingConditions( @@ -120,6 +122,7 @@ data class Zcam( val L_a: Double, val Y_b: Double, ) { + private val absoluteWhitePoint = whitePoint * luminance private val Y_w = absoluteWhitePoint.luminance val F_b = sqrt(Y_b / Y_w) diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/core/ColorUtils.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/core/ColorUtils.kt index 04134bd..1e1cefb 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/core/ColorUtils.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/core/ColorUtils.kt @@ -51,7 +51,7 @@ fun Color.toRgb(): Rgb { fun animateZcamLchAsState( targetValue: ZcamLch, animationSpec: AnimationSpec = spring(), - finishedListener: ((ZcamLch) -> Unit)? = null + finishedListener: ((ZcamLch) -> Unit)? = null, ): State { val converter = remember { TwoWayConverter( diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/core/ZcamLch.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/core/ZcamLch.kt index cd4e8c1..2bfd542 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/core/ZcamLch.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/core/ZcamLch.kt @@ -16,10 +16,12 @@ data class ZcamLch( val C: Double, val h: Double, ) { + @Composable fun toZcam(): Zcam = zcamLch(L = L, C = C, h = h) companion object { + @Composable fun Color.toZcamLch(): ZcamLch = toRgb().toZcam().toZcamLch() diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/data/Illuminant.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/data/Illuminant.kt index b85e9ae..bd6fc6b 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/data/Illuminant.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/data/Illuminant.kt @@ -10,6 +10,7 @@ package me.ash.reader.ui.theme.palette.data import me.ash.reader.ui.theme.palette.colorspace.ciexyz.CieXyz object Illuminant { + /** CIE Illuminant D65 - standard 2º observer. 6504 K color temperature. * Values are calculated from [this table](https://github.com/gpmarques/colorimetry/blob/master/all_1nm_data.xls). */ diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/dynamic/Harmonies.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/dynamic/Harmonies.kt index 5276b4a..ad67477 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/dynamic/Harmonies.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/dynamic/Harmonies.kt @@ -15,7 +15,7 @@ import kotlin.math.sign fun Zcam.harmonizeTowards( target: Zcam, factor: Double = 0.5, - maxHueShift: Double = 15.0 + maxHueShift: Double = 15.0, ): Zcam = copy( hz = hz + ( ((180.0 - abs(abs(hz - target.hz) - 180.0)) * factor).coerceAtMost(maxHueShift) diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/dynamic/WallpaperColors.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/dynamic/WallpaperColors.kt index f7cbf9b..487b5de 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/dynamic/WallpaperColors.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/dynamic/WallpaperColors.kt @@ -15,13 +15,14 @@ import androidx.compose.runtime.Stable import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView -import me.ash.reader.data.preference.LocalCustomPrimaryColor +import me.ash.reader.data.model.preference.LocalCustomPrimaryColor import me.ash.reader.ui.theme.palette.TonalPalettes import me.ash.reader.ui.theme.palette.TonalPalettes.Companion.getSystemTonalPalettes import me.ash.reader.ui.theme.palette.TonalPalettes.Companion.toTonalPalettes import me.ash.reader.ui.theme.palette.safeHexToColor object PresetColor { + val blue = Color(0xFF80BBFF) val pink = Color(0xFFFFD8E4) val purple = Color(0xFF62539f) diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/util/MathUtils.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/util/MathUtils.kt index 0975b3d..6849009 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/util/MathUtils.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/util/MathUtils.kt @@ -15,6 +15,7 @@ class Matrix3( private val y: DoubleArray, private val z: DoubleArray, ) { + fun inverse(): Matrix3 { val det = determinant() return Matrix3( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7e2d670..3d980e8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -108,6 +108,7 @@ Deutsch français Čeština + italiano Tips & support About, open source licenses Welcome