Skip to content

Commit d6bafb6

Browse files
committed
[feature|fix] Support for grouping RSS; fix #18, #19
1 parent bbb85e8 commit d6bafb6

27 files changed

+1269
-373
lines changed

app/build.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ android {
2020
applicationId = "com.skyd.anivu"
2121
minSdk = 24
2222
targetSdk = 34
23-
versionCode = 13
24-
versionName = "1.1-beta12"
23+
versionCode = 14
24+
versionName = "1.1-beta13"
2525

2626
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2727

@@ -148,7 +148,7 @@ dependencies {
148148
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")
149149
implementation("androidx.compose.ui:ui:1.6.5")
150150
implementation("androidx.compose.material:material:1.6.5")
151-
implementation("androidx.compose.material3:material3:1.2.1")
151+
implementation("androidx.compose.material3:material3:1.3.0-alpha05")
152152
implementation("androidx.compose.material3:material3-window-size-class:1.2.1")
153153
implementation("androidx.compose.material:material-icons-extended:1.6.5")
154154
implementation("com.materialkolor:material-kolor:1.4.4")

app/src/main/java/com/skyd/anivu/di/DatabaseModule.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.skyd.anivu.model.db.dao.ArticleDao
77
import com.skyd.anivu.model.db.dao.DownloadInfoDao
88
import com.skyd.anivu.model.db.dao.EnclosureDao
99
import com.skyd.anivu.model.db.dao.FeedDao
10+
import com.skyd.anivu.model.db.dao.GroupDao
1011
import com.skyd.anivu.model.db.dao.SearchDomainDao
1112
import com.skyd.anivu.model.db.dao.SessionParamsDao
1213
import com.skyd.anivu.model.db.dao.TorrentFileDao
@@ -26,6 +27,10 @@ object DatabaseModule {
2627
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase =
2728
AppDatabase.getInstance(context)
2829

30+
@Provides
31+
@Singleton
32+
fun provideGroupDao(database: AppDatabase): GroupDao = database.groupDao()
33+
2934
@Provides
3035
@Singleton
3136
fun provideFeedDao(database: AppDatabase): FeedDao = database.feedDao()
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.skyd.anivu.ext
2+
3+
import androidx.compose.runtime.mutableStateMapOf
4+
import androidx.compose.runtime.saveable.Saver
5+
import androidx.compose.runtime.snapshots.SnapshotStateMap
6+
import androidx.compose.runtime.toMutableStateMap
7+
8+
fun <K, V> snapshotStateMapSaver() = Saver<SnapshotStateMap<K, V>, Any>(
9+
save = { state -> state.toList() },
10+
restore = { value ->
11+
@Suppress("UNCHECKED_CAST")
12+
(value as? List<Pair<K, V>>)?.toMutableStateMap() ?: mutableStateMapOf()
13+
}
14+
)

app/src/main/java/com/skyd/anivu/model/bean/FeedBean.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,18 @@ data class FeedBean(
2525
var link: String? = null,
2626
@ColumnInfo(name = ICON_COLUMN)
2727
var icon: String? = null,
28+
@ColumnInfo(name = GROUP_ID_COLUMN)
29+
var groupId: String? = null,
30+
@ColumnInfo(name = NICKNAME_COLUMN)
31+
var nickname: String? = null,
2832
) : BaseBean, Parcelable {
2933
companion object {
3034
const val URL_COLUMN = "url"
3135
const val TITLE_COLUMN = "title"
3236
const val DESCRIPTION_COLUMN = "description"
3337
const val LINK_COLUMN = "link"
3438
const val ICON_COLUMN = "icon"
39+
const val GROUP_ID_COLUMN = "groupId"
40+
const val NICKNAME_COLUMN = "nickname"
3541
}
3642
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.skyd.anivu.model.bean
2+
3+
import android.os.Parcelable
4+
import androidx.room.ColumnInfo
5+
import androidx.room.Entity
6+
import androidx.room.PrimaryKey
7+
import com.skyd.anivu.R
8+
import com.skyd.anivu.appContext
9+
import com.skyd.anivu.base.BaseBean
10+
import kotlinx.parcelize.Parcelize
11+
import kotlinx.serialization.Serializable
12+
13+
const val GROUP_TABLE_NAME = "Group"
14+
15+
@Parcelize
16+
@Serializable
17+
@Entity(tableName = GROUP_TABLE_NAME)
18+
data class GroupBean(
19+
@PrimaryKey
20+
@ColumnInfo(name = GROUP_ID_COLUMN)
21+
val groupId: String,
22+
@ColumnInfo(name = NAME_COLUMN)
23+
val name: String,
24+
) : BaseBean, Parcelable {
25+
companion object {
26+
const val DEFAULT_GROUP_ID = "default"
27+
28+
const val NAME_COLUMN = "name"
29+
const val GROUP_ID_COLUMN = "groupId"
30+
31+
val defaultGroup = GroupBean(
32+
groupId = DEFAULT_GROUP_ID,
33+
name = appContext.getString(R.string.default_feed_group)
34+
)
35+
}
36+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.skyd.anivu.model.bean
2+
3+
import androidx.room.Embedded
4+
import androidx.room.Relation
5+
6+
/**
7+
* A [group] contains many [feeds].
8+
*/
9+
data class GroupWithFeedBean(
10+
@Embedded
11+
var group: GroupBean,
12+
@Relation(
13+
parentColumn = GroupBean.GROUP_ID_COLUMN,
14+
entityColumn = FeedBean.GROUP_ID_COLUMN,
15+
)
16+
var feeds: List<FeedBean>,
17+
)

app/src/main/java/com/skyd/anivu/model/db/AppDatabase.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import androidx.room.TypeConverters
88
import com.skyd.anivu.model.bean.ArticleBean
99
import com.skyd.anivu.model.bean.EnclosureBean
1010
import com.skyd.anivu.model.bean.FeedBean
11+
import com.skyd.anivu.model.bean.GroupBean
1112
import com.skyd.anivu.model.bean.download.DownloadInfoBean
1213
import com.skyd.anivu.model.bean.download.DownloadLinkUuidMapBean
1314
import com.skyd.anivu.model.bean.download.SessionParamsBean
@@ -16,10 +17,12 @@ import com.skyd.anivu.model.db.dao.ArticleDao
1617
import com.skyd.anivu.model.db.dao.DownloadInfoDao
1718
import com.skyd.anivu.model.db.dao.EnclosureDao
1819
import com.skyd.anivu.model.db.dao.FeedDao
20+
import com.skyd.anivu.model.db.dao.GroupDao
1921
import com.skyd.anivu.model.db.dao.SessionParamsDao
2022
import com.skyd.anivu.model.db.dao.TorrentFileDao
2123
import com.skyd.anivu.model.db.migration.Migration1To2
2224
import com.skyd.anivu.model.db.migration.Migration2To3
25+
import com.skyd.anivu.model.db.migration.Migration3To4
2326

2427
const val APP_DATA_BASE_FILE_NAME = "app.db"
2528

@@ -32,14 +35,15 @@ const val APP_DATA_BASE_FILE_NAME = "app.db"
3235
DownloadLinkUuidMapBean::class,
3336
SessionParamsBean::class,
3437
TorrentFileBean::class,
38+
GroupBean::class,
3539
],
36-
version = 3
40+
version = 4
3741
)
3842
@TypeConverters(
3943
value = []
4044
)
4145
abstract class AppDatabase : RoomDatabase() {
42-
46+
abstract fun groupDao(): GroupDao
4347
abstract fun feedDao(): FeedDao
4448
abstract fun articleDao(): ArticleDao
4549
abstract fun enclosureDao(): EnclosureDao
@@ -51,7 +55,7 @@ abstract class AppDatabase : RoomDatabase() {
5155
@Volatile
5256
private var instance: AppDatabase? = null
5357

54-
private val migrations = arrayOf(Migration1To2(), Migration2To3())
58+
private val migrations = arrayOf(Migration1To2(), Migration2To3(), Migration3To4())
5559

5660
fun getInstance(context: Context): AppDatabase {
5761
return if (instance == null) {

app/src/main/java/com/skyd/anivu/model/db/dao/FeedDao.kt

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import com.skyd.anivu.appContext
1313
import com.skyd.anivu.model.bean.FEED_TABLE_NAME
1414
import com.skyd.anivu.model.bean.FeedBean
1515
import com.skyd.anivu.model.bean.FeedWithArticleBean
16+
import com.skyd.anivu.model.bean.GROUP_TABLE_NAME
17+
import com.skyd.anivu.model.bean.GroupBean
1618
import dagger.hilt.EntryPoint
1719
import dagger.hilt.InstallIn
1820
import dagger.hilt.android.EntryPointAccessors
@@ -63,6 +65,34 @@ interface FeedDao {
6365
@Query("DELETE FROM $FEED_TABLE_NAME WHERE ${FeedBean.URL_COLUMN} = :url")
6466
suspend fun removeFeed(url: String): Int
6567

68+
@Transaction
69+
@Query("DELETE FROM $FEED_TABLE_NAME WHERE ${FeedBean.GROUP_ID_COLUMN} = :groupId")
70+
suspend fun removeFeedByGroupId(groupId: String): Int
71+
72+
@Transaction
73+
@Query(
74+
"""
75+
UPDATE $FEED_TABLE_NAME
76+
SET ${FeedBean.GROUP_ID_COLUMN} = :groupId
77+
WHERE ${FeedBean.URL_COLUMN} = :feedUrl
78+
"""
79+
)
80+
suspend fun updateFeedGroupId(feedUrl: String, groupId: String?): Int
81+
82+
@Transaction
83+
@Query(
84+
"""
85+
UPDATE $FEED_TABLE_NAME
86+
SET ${FeedBean.GROUP_ID_COLUMN} = :toGroupId
87+
WHERE :fromGroupId IS NULL AND ${FeedBean.GROUP_ID_COLUMN} IS NULL OR
88+
${FeedBean.GROUP_ID_COLUMN} = :fromGroupId OR
89+
:fromGroupId IS NULL AND ${FeedBean.GROUP_ID_COLUMN} NOT IN (
90+
SELECT DISTINCT ${GroupBean.GROUP_ID_COLUMN} FROM `$GROUP_TABLE_NAME`
91+
)
92+
"""
93+
)
94+
suspend fun moveFeedToGroup(fromGroupId: String?, toGroupId: String?): Int
95+
6696
@Transaction
6797
@Query("SELECT * FROM $FEED_TABLE_NAME")
6898
fun getFeedPagingSource(): PagingSource<Int, FeedBean>
@@ -71,13 +101,23 @@ interface FeedDao {
71101
@Query("SELECT * FROM $FEED_TABLE_NAME WHERE ${FeedBean.URL_COLUMN} = :feedUrl")
72102
suspend fun getFeed(feedUrl: String): FeedBean
73103

104+
@Transaction
105+
@Query(
106+
"""
107+
SELECT * FROM $FEED_TABLE_NAME
108+
WHERE ${FeedBean.GROUP_ID_COLUMN} IS NULL OR
109+
${FeedBean.GROUP_ID_COLUMN} NOT IN (:groupIds)
110+
"""
111+
)
112+
suspend fun getFeedsNotIn(groupIds: List<String>): List<FeedBean>
113+
74114
@Transaction
75115
@RawQuery(observedEntities = [FeedBean::class])
76116
fun getFeedPagingSource(sql: SupportSQLiteQuery): PagingSource<Int, FeedBean>
77117

78118
@Transaction
79119
@RawQuery(observedEntities = [FeedBean::class])
80-
fun getFeedListPagingSource(sql: SupportSQLiteQuery): List<FeedBean>
120+
fun getFeedList(sql: SupportSQLiteQuery): List<FeedBean>
81121

82122
@Transaction
83123
@Query("SELECT ${FeedBean.URL_COLUMN} FROM $FEED_TABLE_NAME")
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.skyd.anivu.model.db.dao
2+
3+
import androidx.room.Dao
4+
import androidx.room.Delete
5+
import androidx.room.Insert
6+
import androidx.room.OnConflictStrategy
7+
import androidx.room.Query
8+
import androidx.room.Transaction
9+
import com.skyd.anivu.appContext
10+
import com.skyd.anivu.model.bean.GROUP_TABLE_NAME
11+
import com.skyd.anivu.model.bean.GroupBean
12+
import com.skyd.anivu.model.bean.GroupWithFeedBean
13+
import dagger.hilt.EntryPoint
14+
import dagger.hilt.InstallIn
15+
import dagger.hilt.android.EntryPointAccessors
16+
import dagger.hilt.components.SingletonComponent
17+
import kotlinx.coroutines.flow.Flow
18+
19+
@Dao
20+
interface GroupDao {
21+
@EntryPoint
22+
@InstallIn(SingletonComponent::class)
23+
interface GroupDaoEntryPoint {
24+
val feedDao: FeedDao
25+
}
26+
27+
@Transaction
28+
@Insert(onConflict = OnConflictStrategy.REPLACE)
29+
suspend fun setGroup(groupBean: GroupBean)
30+
31+
@Transaction
32+
@Delete
33+
suspend fun removeGroup(groupBean: GroupBean): Int
34+
35+
@Transaction
36+
@Query("DELETE FROM `$GROUP_TABLE_NAME` WHERE ${GroupBean.GROUP_ID_COLUMN} = :groupId")
37+
suspend fun removeGroup(groupId: String): Int
38+
39+
@Transaction
40+
suspend fun removeGroupWithFeed(groupId: String): Int {
41+
removeGroup(groupId)
42+
return EntryPointAccessors.fromApplication(appContext, GroupDaoEntryPoint::class.java).run {
43+
feedDao.removeFeedByGroupId(groupId)
44+
}
45+
}
46+
47+
@Transaction
48+
suspend fun moveGroupFeedsTo(fromGroupId: String?, toGroupId: String?): Int {
49+
return EntryPointAccessors.fromApplication(appContext, GroupDaoEntryPoint::class.java).run {
50+
feedDao.moveFeedToGroup(fromGroupId = fromGroupId, toGroupId = toGroupId)
51+
}
52+
}
53+
54+
@Transaction
55+
@Query("SELECT * FROM `$GROUP_TABLE_NAME`")
56+
fun getGroupWithFeeds(): Flow<List<GroupWithFeedBean>>
57+
58+
@Transaction
59+
@Query("SELECT DISTINCT ${GroupBean.GROUP_ID_COLUMN} FROM `$GROUP_TABLE_NAME`")
60+
fun getGroupIds(): Flow<List<String>>
61+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.skyd.anivu.model.db.migration
2+
3+
import androidx.room.migration.Migration
4+
import androidx.sqlite.db.SupportSQLiteDatabase
5+
import com.skyd.anivu.model.bean.FEED_TABLE_NAME
6+
import com.skyd.anivu.model.bean.FeedBean
7+
import com.skyd.anivu.model.bean.GROUP_TABLE_NAME
8+
import com.skyd.anivu.model.bean.GroupBean
9+
10+
class Migration3To4 : Migration(3, 4) {
11+
override fun migrate(db: SupportSQLiteDatabase) {
12+
db.execSQL(
13+
"""
14+
CREATE TABLE `$GROUP_TABLE_NAME` (
15+
${GroupBean.GROUP_ID_COLUMN} TEXT NOT NULL PRIMARY KEY,
16+
${GroupBean.NAME_COLUMN} TEXT NOT NULL
17+
)
18+
"""
19+
)
20+
db.execSQL("ALTER TABLE $FEED_TABLE_NAME ADD ${FeedBean.GROUP_ID_COLUMN} TEXT")
21+
db.execSQL("ALTER TABLE $FEED_TABLE_NAME ADD ${FeedBean.NICKNAME_COLUMN} TEXT")
22+
}
23+
}

0 commit comments

Comments
 (0)