From 803bd1efae0225f2f0cfee0e773f192cac632bd0 Mon Sep 17 00:00:00 2001 From: paulcoding810 Date: Fri, 21 Feb 2025 13:09:01 +0700 Subject: [PATCH 1/2] Merge history and favorite table into post_items table --- .../5.json | 32 +++++- .../6.json | 100 ++++++++++++++++++ .../hviewer/database/AppDatabase.kt | 6 +- .../hviewer/database/DatabaseProvider.kt | 2 +- .../hviewer/database/FavoriteDao.kt | 21 ---- .../paulcoding/hviewer/database/HistoryDao.kt | 24 ----- .../paulcoding/hviewer/database/Migrations.kt | 37 ++++++- .../hviewer/database/PostItemDao.kt | 19 ++++ .../com/paulcoding/hviewer/model/PostModel.kt | 35 +----- .../hviewer/ui/page/AppViewModel.kt | 41 +++---- .../hviewer/ui/page/history/HistoryPage.kt | 12 +-- 11 files changed, 211 insertions(+), 118 deletions(-) create mode 100644 app/schemas/com.paulcoding.hviewer.database.AppDatabase/6.json delete mode 100644 app/src/main/java/com/paulcoding/hviewer/database/FavoriteDao.kt delete mode 100644 app/src/main/java/com/paulcoding/hviewer/database/HistoryDao.kt create mode 100644 app/src/main/java/com/paulcoding/hviewer/database/PostItemDao.kt diff --git a/app/schemas/com.paulcoding.hviewer.database.AppDatabase/5.json b/app/schemas/com.paulcoding.hviewer.database.AppDatabase/5.json index 9d561f8..71c61c8 100644 --- a/app/schemas/com.paulcoding.hviewer.database.AppDatabase/5.json +++ b/app/schemas/com.paulcoding.hviewer.database.AppDatabase/5.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 5, - "identityHash": "d30eb7c5c1c1735782bd48febc6f9594", + "identityHash": "4e5007c1daa16a91f4f17cc9716ee605", "entities": [ { - "tableName": "favorite_posts", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `name` TEXT NOT NULL, `thumbnail` TEXT NOT NULL, `site` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `tags` TEXT, `size` INTEGER, `views` INTEGER, `quantity` INTEGER, PRIMARY KEY(`url`))", + "tableName": "post_items", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `name` TEXT NOT NULL, `thumbnail` TEXT NOT NULL, `site` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `tags` TEXT, `size` INTEGER, `views` INTEGER, `quantity` INTEGER, `favorite` INTEGER NOT NULL, `favoriteAt` INTEGER NOT NULL, `viewed` INTEGER NOT NULL, `viewedAt` INTEGER NOT NULL, PRIMARY KEY(`url`))", "fields": [ { "fieldPath": "url", @@ -61,6 +61,30 @@ "columnName": "quantity", "affinity": "INTEGER", "notNull": false + }, + { + "fieldPath": "favorite", + "columnName": "favorite", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favoriteAt", + "columnName": "favoriteAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "viewed", + "columnName": "viewed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "viewedAt", + "columnName": "viewedAt", + "affinity": "INTEGER", + "notNull": true } ], "primaryKey": { @@ -144,7 +168,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd30eb7c5c1c1735782bd48febc6f9594')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4e5007c1daa16a91f4f17cc9716ee605')" ] } } \ No newline at end of file diff --git a/app/schemas/com.paulcoding.hviewer.database.AppDatabase/6.json b/app/schemas/com.paulcoding.hviewer.database.AppDatabase/6.json new file mode 100644 index 0000000..86c3cbf --- /dev/null +++ b/app/schemas/com.paulcoding.hviewer.database.AppDatabase/6.json @@ -0,0 +1,100 @@ +{ + "formatVersion": 1, + "database": { + "version": 6, + "identityHash": "9a533ec4d7da1d93de96fb8ff43d17b9", + "entities": [ + { + "tableName": "post_items", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `name` TEXT NOT NULL, `thumbnail` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `tags` TEXT, `size` INTEGER, `views` INTEGER, `quantity` INTEGER, `favorite` INTEGER NOT NULL, `favoriteAt` INTEGER NOT NULL, `viewed` INTEGER NOT NULL, `viewedAt` INTEGER NOT NULL, PRIMARY KEY(`url`))", + "fields": [ + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnail", + "columnName": "thumbnail", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tags", + "columnName": "tags", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "views", + "columnName": "views", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "quantity", + "columnName": "quantity", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "favorite", + "columnName": "favorite", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favoriteAt", + "columnName": "favoriteAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "viewed", + "columnName": "viewed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "viewedAt", + "columnName": "viewedAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "url" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9a533ec4d7da1d93de96fb8ff43d17b9')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/paulcoding/hviewer/database/AppDatabase.kt b/app/src/main/java/com/paulcoding/hviewer/database/AppDatabase.kt index aa9eeb0..8b21baf 100644 --- a/app/src/main/java/com/paulcoding/hviewer/database/AppDatabase.kt +++ b/app/src/main/java/com/paulcoding/hviewer/database/AppDatabase.kt @@ -3,12 +3,10 @@ package com.paulcoding.hviewer.database import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters -import com.paulcoding.hviewer.model.PostHistory import com.paulcoding.hviewer.model.PostItem -@Database(entities = [PostItem::class, PostHistory::class], version = 5, exportSchema = true) +@Database(entities = [PostItem::class], version = 6, exportSchema = true) @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { - abstract fun favoritePostDao(): FavoritePostDao - abstract fun historyDao(): HistoryDao + abstract fun postItemDao(): PostItemDao } \ No newline at end of file diff --git a/app/src/main/java/com/paulcoding/hviewer/database/DatabaseProvider.kt b/app/src/main/java/com/paulcoding/hviewer/database/DatabaseProvider.kt index e0ea7c0..8493ab0 100644 --- a/app/src/main/java/com/paulcoding/hviewer/database/DatabaseProvider.kt +++ b/app/src/main/java/com/paulcoding/hviewer/database/DatabaseProvider.kt @@ -12,7 +12,7 @@ object DatabaseProvider { appContext, AppDatabase::class.java, "hviewer_db" ) - .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5) + .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6) .build() } return db!! diff --git a/app/src/main/java/com/paulcoding/hviewer/database/FavoriteDao.kt b/app/src/main/java/com/paulcoding/hviewer/database/FavoriteDao.kt deleted file mode 100644 index fdaa946..0000000 --- a/app/src/main/java/com/paulcoding/hviewer/database/FavoriteDao.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.paulcoding.hviewer.database - -import androidx.room.Dao -import androidx.room.Delete -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import com.paulcoding.hviewer.model.PostItem -import kotlinx.coroutines.flow.Flow - -@Dao -interface FavoritePostDao { - @Query("SELECT * FROM favorite_posts ORDER BY createdAt DESC") - fun getAll(): Flow> - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(post: PostItem) - - @Delete - suspend fun delete(post: PostItem) -} \ No newline at end of file diff --git a/app/src/main/java/com/paulcoding/hviewer/database/HistoryDao.kt b/app/src/main/java/com/paulcoding/hviewer/database/HistoryDao.kt deleted file mode 100644 index 02aa2c6..0000000 --- a/app/src/main/java/com/paulcoding/hviewer/database/HistoryDao.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.paulcoding.hviewer.database - -import androidx.room.Dao -import androidx.room.Delete -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import com.paulcoding.hviewer.model.PostHistory -import kotlinx.coroutines.flow.Flow - -@Dao -interface HistoryDao { - @Query("SELECT * FROM history ORDER BY createdAt DESC") - fun getAll(): Flow> - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(history: PostHistory) - - @Delete - suspend fun delete(history: PostHistory) - - @Query("DELETE FROM history WHERE url = (SELECT url FROM history ORDER BY createdAt ASC LIMIT 1)") - suspend fun deleteOldest() -} \ No newline at end of file diff --git a/app/src/main/java/com/paulcoding/hviewer/database/Migrations.kt b/app/src/main/java/com/paulcoding/hviewer/database/Migrations.kt index e985b37..c9e5293 100644 --- a/app/src/main/java/com/paulcoding/hviewer/database/Migrations.kt +++ b/app/src/main/java/com/paulcoding/hviewer/database/Migrations.kt @@ -30,4 +30,39 @@ val MIGRATION_4_5 = object : Migration(4, 5) { "CREATE TABLE IF NOT EXISTS `history` (`url` TEXT NOT NULL, `name` TEXT NOT NULL, `thumbnail` TEXT NOT NULL, `site` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `tags` TEXT, `size` INTEGER, `views` INTEGER, `quantity` INTEGER, PRIMARY KEY(`url`))" ) } -} \ No newline at end of file +} + +val MIGRATION_5_6 = object : Migration(5, 6) { + override fun migrate(db: SupportSQLiteDatabase) { + // Step 1: Create a new table post_items + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS post_items ( + url TEXT NOT NULL, + name TEXT NOT NULL, + thumbnail TEXT NOT NULL, + createdAt INTEGER NOT NULL, + tags TEXT, + size INTEGER, + views INTEGER, + quantity INTEGER, + favorite INTEGER NOT NULL, + favoriteAt INTEGER NOT NULL, + viewed INTEGER NOT NULL, + viewedAt INTEGER NOT NULL, + PRIMARY KEY(`url`) + ) + """.trimIndent() + ) + + // Step 2: Copy data from favorite_posts to post_items + db.execSQL(""" + INSERT INTO post_items (url, name, thumbnail, createdAt, tags, size, views, quantity, favorite, favoriteAt, viewed, viewedAt) + SELECT url, name, thumbnail, createdAt, tags, size, views, quantity, 1, createdAt, 1, createdAt FROM favorite_posts + """.trimIndent()) + + // Step 3: Drop tables + db.execSQL("DROP TABLE favorite_posts") + db.execSQL("DROP TABLE history") + } +} diff --git a/app/src/main/java/com/paulcoding/hviewer/database/PostItemDao.kt b/app/src/main/java/com/paulcoding/hviewer/database/PostItemDao.kt new file mode 100644 index 0000000..461f486 --- /dev/null +++ b/app/src/main/java/com/paulcoding/hviewer/database/PostItemDao.kt @@ -0,0 +1,19 @@ +package com.paulcoding.hviewer.database + +import androidx.room.Dao +import androidx.room.Query +import androidx.room.Update +import com.paulcoding.hviewer.model.PostItem +import kotlinx.coroutines.flow.Flow + +@Dao +interface PostItemDao { + @Query("SELECT * FROM post_items WHERE favorite = 1 ORDER BY favoriteAt DESC") + fun getFavoritePosts(): Flow> + + @Query("SELECT * FROM post_items where viewed = 1 ORDER BY viewedAt DESC LIMIT 20") + fun getViewedPosts(): Flow> + + @Update + suspend fun updatePost(postItem: PostItem) +} \ No newline at end of file diff --git a/app/src/main/java/com/paulcoding/hviewer/model/PostModel.kt b/app/src/main/java/com/paulcoding/hviewer/model/PostModel.kt index 0d8e7a6..707e9ad 100644 --- a/app/src/main/java/com/paulcoding/hviewer/model/PostModel.kt +++ b/app/src/main/java/com/paulcoding/hviewer/model/PostModel.kt @@ -10,18 +10,21 @@ data class PostData( ) -@Entity(tableName = "favorite_posts") +@Entity(tableName = "post_items") data class PostItem( @PrimaryKey val url: String = "", val name: String = "", val thumbnail: String = "", - val site: String = "", val createdAt: Long = 0, val tags: List? = null, val size: Int? = null, val views: Int? = null, val quantity: Int? = null, + val favorite: Boolean = false, + val favoriteAt: Long = 0, + val viewed: Boolean = false, + val viewedAt: Long = 0, ) { fun getHost(): String { return url.split("/").getOrNull(2) ?: "" @@ -33,34 +36,6 @@ data class PostItem( } } -// duplicated? -@Entity(tableName = "history") -data class PostHistory( - @PrimaryKey - val url: String = "", - val name: String = "", - val thumbnail: String = "", - val site: String = "", - val createdAt: Long = 0, - val tags: List? = null, - val size: Int? = null, - val views: Int? = null, - val quantity: Int? = null, -) { - fun toPostItem(): PostItem { - return PostItem( - url = url, - name = name, - thumbnail = thumbnail, - site = site, - createdAt = createdAt, - tags = tags, - size = size, - views = views - ) - } -} - data class Tag( val name: String = "", val url: String = "", diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/AppViewModel.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/AppViewModel.kt index d28500e..99aa5b3 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/AppViewModel.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/AppViewModel.kt @@ -7,14 +7,12 @@ import com.paulcoding.hviewer.MainApp.Companion.appContext import com.paulcoding.hviewer.database.DatabaseProvider import com.paulcoding.hviewer.helper.crashLogDir import com.paulcoding.hviewer.helper.scriptsDir -import com.paulcoding.hviewer.model.PostHistory import com.paulcoding.hviewer.model.PostItem import com.paulcoding.hviewer.model.SiteConfig import com.paulcoding.hviewer.model.Tag import com.paulcoding.hviewer.network.Github import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import java.io.File @@ -32,8 +30,8 @@ class AppViewModel : ViewModel() { private var _tabs = MutableStateFlow(listOf()) val tabs = _tabs.asStateFlow() - val favoritePosts = DatabaseProvider.getInstance().favoritePostDao().getAll() - val historyPosts = DatabaseProvider.getInstance().historyDao().getAll() + val favoritePosts = DatabaseProvider.getInstance().postItemDao().getFavoritePosts() + val historyPosts = DatabaseProvider.getInstance().postItemDao().getViewedPosts() fun setCurrentPost(post: PostItem) { _stateFlow.update { it.copy(post = post) } @@ -64,43 +62,34 @@ class AppViewModel : ViewModel() { fun addFavorite(postItem: PostItem, reAdded: Boolean = false) { viewModelScope.launch { - val item = if (reAdded) postItem else postItem.copy( - createdAt = System.currentTimeMillis() + val item = postItem.copy( + favorite = true, + favoriteAt = if (!reAdded) System.currentTimeMillis() else postItem.favoriteAt, ) - DatabaseProvider.getInstance().favoritePostDao().insert(item) + DatabaseProvider.getInstance().postItemDao().updatePost(item) } } fun deleteFavorite(postItem: PostItem) { viewModelScope.launch { - DatabaseProvider.getInstance().favoritePostDao().delete(postItem) + DatabaseProvider.getInstance().postItemDao().updatePost( + postItem.copy(favorite = false) + ) } } fun addHistory(postItem: PostItem) { - val item = PostHistory( - createdAt = System.currentTimeMillis(), - url = postItem.url, - views = postItem.views, - thumbnail = postItem.thumbnail, - name = postItem.name, - size = postItem.size, - tags = postItem.tags, - quantity = postItem.quantity - ) viewModelScope.launch { - // limit to 25 items in history - if (historyPosts.first().size >= 25) { - DatabaseProvider.getInstance().historyDao().deleteOldest() - } - DatabaseProvider.getInstance().historyDao() - .insert(item) + DatabaseProvider.getInstance().postItemDao() + .updatePost(postItem.copy(viewed = true, viewedAt = System.currentTimeMillis())) } } - fun deleteHistory(history: PostHistory) { + fun deleteHistory(postItem: PostItem) { viewModelScope.launch { - DatabaseProvider.getInstance().historyDao().delete(history) + DatabaseProvider.getInstance().postItemDao() + .updatePost(postItem.copy(viewed = false)) + } } diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/history/HistoryPage.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/history/HistoryPage.kt index cb05781..6c48c12 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/history/HistoryPage.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/history/HistoryPage.kt @@ -19,7 +19,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.paulcoding.hviewer.R -import com.paulcoding.hviewer.model.PostHistory import com.paulcoding.hviewer.model.PostItem import com.paulcoding.hviewer.model.Tag import com.paulcoding.hviewer.ui.component.HBackIcon @@ -34,7 +33,7 @@ fun HistoryPage( goBack: () -> Unit, appViewModel: AppViewModel, navToImages: (PostItem) -> Unit, navToCustomTag: (PostItem, Tag) -> Unit, - deleteHistory: (post: PostHistory) -> Unit + deleteHistory: (post: PostItem) -> Unit ) { val historyPosts by appViewModel.historyPosts.collectAsState(initial = listOf()) @@ -55,12 +54,12 @@ fun HistoryPage( ) { items(historyPosts) { PostCard( - postItem = it.toPostItem(), + postItem = it, onTagClick = { tag -> - navToCustomTag(it.toPostItem(), tag) + navToCustomTag(it, tag) }, onClick = { - navToImages(it.toPostItem()) + navToImages(it) }) { HIcon( Icons.Outlined.Delete, @@ -79,5 +78,4 @@ fun HistoryPage( ) } } -} - +} \ No newline at end of file From 7aaa0af996aa5036f5a33ce2ac09753210dc5afb Mon Sep 17 00:00:00 2001 From: paulcoding810 Date: Fri, 21 Feb 2025 13:25:32 +0700 Subject: [PATCH 2/2] Store posts to post_items table for each fetch --- .../java/com/paulcoding/hviewer/database/PostItemDao.kt | 8 ++++++++ .../paulcoding/hviewer/ui/page/posts/PostsViewModel.kt | 3 +++ 2 files changed, 11 insertions(+) diff --git a/app/src/main/java/com/paulcoding/hviewer/database/PostItemDao.kt b/app/src/main/java/com/paulcoding/hviewer/database/PostItemDao.kt index 461f486..0702c79 100644 --- a/app/src/main/java/com/paulcoding/hviewer/database/PostItemDao.kt +++ b/app/src/main/java/com/paulcoding/hviewer/database/PostItemDao.kt @@ -1,6 +1,8 @@ package com.paulcoding.hviewer.database import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Update import com.paulcoding.hviewer.model.PostItem @@ -11,6 +13,12 @@ interface PostItemDao { @Query("SELECT * FROM post_items WHERE favorite = 1 ORDER BY favoriteAt DESC") fun getFavoritePosts(): Flow> + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun addPost(postItem: PostItem) + + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun addPosts(postItems: List) + @Query("SELECT * FROM post_items where viewed = 1 ORDER BY viewedAt DESC LIMIT 20") fun getViewedPosts(): Flow> diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostsViewModel.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostsViewModel.kt index efc6d1c..b7caa7d 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostsViewModel.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostsViewModel.kt @@ -3,6 +3,7 @@ package com.paulcoding.hviewer.ui.page.posts import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import com.paulcoding.hviewer.database.DatabaseProvider import com.paulcoding.hviewer.helper.SCRIPTS_DIR import com.paulcoding.hviewer.model.PostItem import com.paulcoding.hviewer.model.Posts @@ -60,6 +61,8 @@ class PostsViewModel(siteConfig: SiteConfig, tag: Tag) : ViewModel() { nextPage = postsData.next ) } + // store all posts to database. + DatabaseProvider.getInstance().postItemDao().addPosts(postsData.posts) } .onFailure { setError(it)