diff --git a/app/schemas/com.paulcoding.hviewer.database.AppDatabase/3.json b/app/schemas/com.paulcoding.hviewer.database.AppDatabase/3.json index 1117531..26f2eca 100644 --- a/app/schemas/com.paulcoding.hviewer.database.AppDatabase/3.json +++ b/app/schemas/com.paulcoding.hviewer.database.AppDatabase/3.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 3, - "identityHash": "40109ede6ba29fdee1d23735114a2981", + "identityHash": "374e52582fb9d67d7560b3849abda364", "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, PRIMARY KEY(`url`))", + "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 NOT NULL, `size` TEXT, `quantity` TEXT, PRIMARY KEY(`url`))", "fields": [ { "fieldPath": "url", @@ -37,6 +37,24 @@ "columnName": "createdAt", "affinity": "INTEGER", "notNull": true + }, + { + "fieldPath": "tags", + "columnName": "tags", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "quantity", + "columnName": "quantity", + "affinity": "TEXT", + "notNull": false } ], "primaryKey": { @@ -52,7 +70,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, '40109ede6ba29fdee1d23735114a2981')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '374e52582fb9d67d7560b3849abda364')" ] } } \ No newline at end of file diff --git a/app/schemas/com.paulcoding.hviewer.database.AppDatabase/4.json b/app/schemas/com.paulcoding.hviewer.database.AppDatabase/4.json new file mode 100644 index 0000000..2d73f01 --- /dev/null +++ b/app/schemas/com.paulcoding.hviewer.database.AppDatabase/4.json @@ -0,0 +1,82 @@ +{ + "formatVersion": 1, + "database": { + "version": 4, + "identityHash": "9cae14220c3b54c73ec622e03a0d6f08", + "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`))", + "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": "site", + "columnName": "site", + "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 + } + ], + "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, '9cae14220c3b54c73ec622e03a0d6f08')" + ] + } +} \ 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 ff938f9..55de46e 100644 --- a/app/src/main/java/com/paulcoding/hviewer/database/AppDatabase.kt +++ b/app/src/main/java/com/paulcoding/hviewer/database/AppDatabase.kt @@ -2,9 +2,11 @@ package com.paulcoding.hviewer.database import androidx.room.Database import androidx.room.RoomDatabase +import androidx.room.TypeConverters import com.paulcoding.hviewer.model.PostItem -@Database(entities = [PostItem::class], version = 3, exportSchema = true) +@Database(entities = [PostItem::class], version = 4, exportSchema = true) +@TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { abstract fun favoritePostDao(): FavoritePostDao } \ No newline at end of file diff --git a/app/src/main/java/com/paulcoding/hviewer/database/Converters.kt b/app/src/main/java/com/paulcoding/hviewer/database/Converters.kt new file mode 100644 index 0000000..de80ef3 --- /dev/null +++ b/app/src/main/java/com/paulcoding/hviewer/database/Converters.kt @@ -0,0 +1,21 @@ +package com.paulcoding.hviewer.database + +import androidx.room.TypeConverter +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.paulcoding.hviewer.model.Tag + +class Converters { + @TypeConverter + fun fromListTagToString(list: List?): String? { + return list?.let { Gson().toJson(it) } + } + + @TypeConverter + fun fromStringToListTag(data: String?): List? { + return data?.let { + val listType = object : TypeToken>() {}.type + Gson().fromJson(it, listType) + } + } +} 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 3acf68c..234dff2 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) + .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4) .build() } return db!! 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 172fb76..cc2565a 100644 --- a/app/src/main/java/com/paulcoding/hviewer/database/Migrations.kt +++ b/app/src/main/java/com/paulcoding/hviewer/database/Migrations.kt @@ -13,4 +13,13 @@ val MIGRATION_2_3 = object : Migration(2, 3) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("ALTER TABLE favorite_posts ADD COLUMN createdAt INTEGER NOT NULL DEFAULT 0") } +} + +val MIGRATION_3_4 = object : Migration(3, 4) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE favorite_posts ADD COLUMN tags TEXT") + db.execSQL("ALTER TABLE favorite_posts ADD COLUMN size INTEGER") + db.execSQL("ALTER TABLE favorite_posts ADD COLUMN views INTEGER") + db.execSQL("ALTER TABLE favorite_posts ADD COLUMN quantity INTEGER") + } } \ 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 4dfe424..f91ea9b 100644 --- a/app/src/main/java/com/paulcoding/hviewer/model/PostModel.kt +++ b/app/src/main/java/com/paulcoding/hviewer/model/PostModel.kt @@ -1,6 +1,6 @@ package com.paulcoding.hviewer.model - import androidx.room.Entity +import androidx.room.Entity import androidx.room.PrimaryKey data class PostData( @@ -18,6 +18,15 @@ data class PostItem( 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, +) + +data class Tag( + val name: String = "", + val url: String = "", ) data class Posts( diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/favorite/FavoritePage.kt b/app/src/main/java/com/paulcoding/hviewer/ui/favorite/FavoritePage.kt index c2cff83..3ea7e22 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/favorite/FavoritePage.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/favorite/FavoritePage.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.lifecycle.viewmodel.compose.viewModel import com.paulcoding.hviewer.model.PostItem +import com.paulcoding.hviewer.model.Tag import com.paulcoding.hviewer.ui.component.HBackIcon import com.paulcoding.hviewer.ui.component.HEmpty import com.paulcoding.hviewer.ui.page.AppViewModel @@ -31,6 +32,7 @@ import kotlinx.coroutines.launch fun FavoritePage( appViewModel: AppViewModel, navToImages: (PostItem) -> Unit, + navToCustomTag: (PostItem, Tag) -> Unit, goBack: () -> Boolean ) { val viewModel: AppViewModel = viewModel() @@ -68,9 +70,11 @@ fun FavoritePage( }) { paddings -> LazyColumn(modifier = Modifier.padding(paddings)) { items(items = favoritePosts, key = { it.url }) { item -> - FavoriteItem(item, navToImages = { navToImages(item) }, deleteFavorite = { - onDelete(item) - }) + FavoriteItem(item, navToImages = { navToImages(item) }, + onTagClick = { tag -> navToCustomTag(item, tag) }, + deleteFavorite = { + onDelete(item) + }) } if (favoritePosts.isEmpty()) item { HEmpty() } @@ -83,11 +87,16 @@ fun FavoritePage( fun FavoriteItem( post: PostItem, navToImages: () -> Unit, + onTagClick: (Tag) -> Unit, deleteFavorite: () -> Unit ) { - PostCard(post, isFavorite = true, setFavorite = { - deleteFavorite() - }) { + PostCard( + post, isFavorite = true, + setFavorite = { + deleteFavorite() + }, + onTagClick = { onTagClick(it) }, + ) { navToImages() } } diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/AppEntry.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/AppEntry.kt index 5382c09..1f09147 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/AppEntry.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/AppEntry.kt @@ -20,6 +20,7 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.paulcoding.hviewer.model.PostItem import com.paulcoding.hviewer.model.SiteConfigs +import com.paulcoding.hviewer.model.Tag import com.paulcoding.hviewer.network.Github import com.paulcoding.hviewer.preference.Preferences import com.paulcoding.hviewer.ui.favorite.FavoritePage @@ -27,6 +28,7 @@ import com.paulcoding.hviewer.ui.page.editor.EditorPage import com.paulcoding.hviewer.ui.page.editor.ListScriptPage import com.paulcoding.hviewer.ui.page.lock.LockPage import com.paulcoding.hviewer.ui.page.post.PostPage +import com.paulcoding.hviewer.ui.page.posts.CustomTagPage import com.paulcoding.hviewer.ui.page.posts.PostsPage import com.paulcoding.hviewer.ui.page.search.SearchPage import com.paulcoding.hviewer.ui.page.settings.SettingsPage @@ -46,6 +48,11 @@ fun AppEntry() { navController.navigate(Route.POST) } + fun navToCustomTag(tag: Tag) { + appViewModel.setCurrentTag(tag) + navController.navigate(Route.CUSTOM_TAG) + } + val startDestination = remember { if (Preferences.pin.isNotEmpty()) Route.LOCK else Route.SITES } @@ -88,9 +95,19 @@ fun AppEntry() { navToImages(post) }, navToSearch = { navController.navigate(Route.SEARCH) }, + navToCustomTag = { navToCustomTag(it) }, goBack = { navController.popBackStack() }, ) } + animatedComposable(Route.CUSTOM_TAG) { + CustomTagPage( + appViewModel, + navToCustomTag = { navToCustomTag(it) }, + goBack = { navController.popBackStack() } + ) { + navToImages(it) + } + } animatedComposable(Route.POST) { PostPage( appViewModel, @@ -104,6 +121,7 @@ fun AppEntry() { navToImages = { post: PostItem -> navToImages(post) }, + navToCustomTag = { navToCustomTag(it) }, goBack = { navController.popBackStack() }, ) } @@ -114,6 +132,10 @@ fun AppEntry() { appViewModel.setSiteConfig(post.site, siteConfigs.sites[post.site]!!) navToImages(post) }, + navToCustomTag = { post, tag -> + appViewModel.setSiteConfig(post.site, siteConfigs.sites[post.site]!!) + navToCustomTag(tag) + }, goBack = { navController.popBackStack() } ) } 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 bbcf5f6..e98c1c6 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 @@ -5,11 +5,11 @@ import androidx.lifecycle.viewModelScope import com.paulcoding.hviewer.BuildConfig import com.paulcoding.hviewer.MainApp.Companion.appContext import com.paulcoding.hviewer.database.DatabaseProvider -import com.paulcoding.hviewer.helper.alsoLog import com.paulcoding.hviewer.helper.crashLogDir import com.paulcoding.hviewer.helper.scriptsDir import com.paulcoding.hviewer.model.PostItem import com.paulcoding.hviewer.model.SiteConfig +import com.paulcoding.hviewer.model.Tag import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update @@ -32,6 +32,12 @@ class AppViewModel : ViewModel() { _stateFlow.update { it.copy(post = post) } } + fun setCurrentTag(tag: Tag) { + _stateFlow.update { it.copy(tag = tag) } + } + + fun getCurrentTag() = _stateFlow.value.tag + fun setSiteConfig(site: String, siteConfig: SiteConfig) { _stateFlow.update { it.copy(site = site to siteConfig) } } @@ -40,7 +46,8 @@ class AppViewModel : ViewModel() { data class UiState( val post: PostItem = PostItem(), val site: Pair = "" to SiteConfig(), - val isDevMode: Boolean = BuildConfig.DEBUG.alsoLog("debug"), + val tag: Tag = Tag(), + val isDevMode: Boolean = BuildConfig.DEBUG, ) fun setDevMode(isDevMode: Boolean) { diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/Route.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/Route.kt index cbb27f8..8a630ef 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/Route.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/Route.kt @@ -5,6 +5,7 @@ object Route { const val SITES = "sites" const val TOPICS = "topics" const val POSTS = "posts" + const val CUSTOM_TAG = "custom_tag" const val POST = "post" const val SEARCH = "search" const val SETTINGS = "settings" diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/CustomTagPage.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/CustomTagPage.kt new file mode 100644 index 0000000..8dcff2e --- /dev/null +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/CustomTagPage.kt @@ -0,0 +1,66 @@ +package com.paulcoding.hviewer.ui.page.posts + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import com.paulcoding.hviewer.extensions.toCapital +import com.paulcoding.hviewer.model.PostItem +import com.paulcoding.hviewer.model.Tag +import com.paulcoding.hviewer.ui.component.HBackIcon +import com.paulcoding.hviewer.ui.component.HPageProgress +import com.paulcoding.hviewer.ui.page.AppViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun CustomTagPage( + appViewModel: AppViewModel, + goBack: () -> Unit, + navToCustomTag: (Tag) -> Unit, + navToImages: (PostItem) -> Unit +) { + val appState by appViewModel.stateFlow.collectAsState() + val tag = appViewModel.getCurrentTag() + var pageProgress by remember { mutableStateOf(1 to 1) } + + Scaffold( + topBar = { + TopAppBar( + title = { Text(tag.name.toCapital()) }, + navigationIcon = { + HBackIcon { goBack() } + }, + actions = { + HPageProgress(pageProgress.first, pageProgress.second) + } + ) + } + ) { paddings -> + Box(modifier = Modifier.padding(paddings)) { + + PageContent( + appViewModel, + siteConfig = appState.site.second, + tag = tag, + navToCustomTag = { + if (it.name != tag.name) + navToCustomTag(it) + }, + onPageChange = { currentPage, total -> + pageProgress = currentPage to total + }) { post -> + navToImages(post) + } + } + } +} + diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostCard.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostCard.kt new file mode 100644 index 0000000..084c517 --- /dev/null +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostCard.kt @@ -0,0 +1,136 @@ +package com.paulcoding.hviewer.ui.page.posts + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Info +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.paulcoding.hviewer.model.PostItem +import com.paulcoding.hviewer.model.Tag +import com.paulcoding.hviewer.ui.component.HFavoriteIcon +import com.paulcoding.hviewer.ui.component.HIcon +import com.paulcoding.hviewer.ui.component.HImage + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun PostCard( + postItem: PostItem, + isFavorite: Boolean = false, + setFavorite: (Boolean) -> Unit = {}, + onTagClick: (Tag) -> Unit = {}, + viewPost: () -> Unit, +) { + var isBottomSheetVisible by remember { mutableStateOf(false) } + val bottomSheetState = rememberModalBottomSheetState() + + LaunchedEffect(isBottomSheetVisible) { + if (isBottomSheetVisible) { + bottomSheetState.show() + } else { + bottomSheetState.hide() + } + } + + Card( + elevation = CardDefaults.cardElevation(4.dp), + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 12.dp), + border = CardDefaults.outlinedCardBorder(), + shape = CardDefaults.outlinedShape, + onClick = { viewPost() }, + ) { + Box( + modifier = Modifier + .fillMaxSize() + .animateContentSize( + animationSpec = tween(durationMillis = 300) + ), + ) { + Column(modifier = Modifier.padding(8.dp)) { + HImage( + url = postItem.thumbnail + ) + Text(postItem.name, fontSize = 12.sp) + } + HFavoriteIcon( + modifier = Modifier.align(Alignment.TopEnd), + isFavorite = isFavorite + ) { + setFavorite(!isFavorite) + } + HIcon(Icons.Outlined.Info) { + isBottomSheetVisible = true + } + } + } + +// TODO: Remove AnimatedVisibility + AnimatedVisibility(isBottomSheetVisible) { + ModalBottomSheet( + sheetState = bottomSheetState, + onDismissRequest = { + isBottomSheetVisible = false + } + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) { + postItem.run { + SelectionContainer { + Text(text = name, fontSize = 20.sp) + } + Text( + text = url, + textDecoration = TextDecoration.Underline, + fontSize = 12.sp, + color = Color.Blue + ) + if (size != null) { + Text(text = "Size: $size") + } + if (views != null) { + Text(text = "Views: $views") + } + postItem.tags?.run { + forEach { tag -> + TextButton(onClick = { + isBottomSheetVisible = false + onTagClick(tag) + }) { + Text(text = tag.name) + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostsPage.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostsPage.kt index cd78933..808bb59 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostsPage.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostsPage.kt @@ -1,8 +1,6 @@ package com.paulcoding.hviewer.ui.page.posts import android.widget.Toast -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -15,8 +13,6 @@ import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Search -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -32,22 +28,20 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import com.paulcoding.hviewer.MainApp.Companion.appContext import com.paulcoding.hviewer.extensions.isScrolledToEnd import com.paulcoding.hviewer.extensions.toCapital +import com.paulcoding.hviewer.helper.log import com.paulcoding.hviewer.model.PostItem import com.paulcoding.hviewer.model.SiteConfig +import com.paulcoding.hviewer.model.Tag import com.paulcoding.hviewer.ui.component.HBackIcon import com.paulcoding.hviewer.ui.component.HEmpty -import com.paulcoding.hviewer.ui.component.HFavoriteIcon import com.paulcoding.hviewer.ui.component.HGoTop import com.paulcoding.hviewer.ui.component.HIcon -import com.paulcoding.hviewer.ui.component.HImage import com.paulcoding.hviewer.ui.component.HLoading import com.paulcoding.hviewer.ui.component.HPageProgress import com.paulcoding.hviewer.ui.page.AppViewModel @@ -60,20 +54,23 @@ fun PostsPage( appViewModel: AppViewModel, navToImages: (PostItem) -> Unit, navToSearch: () -> Unit, + navToCustomTag: (Tag) -> Unit, goBack: () -> Unit ) { val appState by appViewModel.stateFlow.collectAsState() val siteConfig = appState.site.second - val listTopic = siteConfig.tags.keys.toList() - val pagerState = rememberPagerState { listTopic.size } + val listTag: List = siteConfig.tags.keys.map { key -> + Tag(name = key, url = siteConfig.tags[key]!!) + } + val pagerState = rememberPagerState { listTag.size } val selectedTabIndex = pagerState.currentPage - val currentPage = listTopic[selectedTabIndex] + val currentTag = listTag[selectedTabIndex] val scope = rememberCoroutineScope() var pageProgress by remember { mutableStateOf(1 to 1) } Scaffold(topBar = { - TopAppBar(title = { Text(currentPage.toCapital()) }, navigationIcon = { + TopAppBar(title = { Text(currentTag.name.toCapital()) }, navigationIcon = { HBackIcon { goBack() } }, actions = { HPageProgress(pageProgress.first, pageProgress.second) @@ -86,7 +83,7 @@ fun PostsPage( modifier = Modifier.fillMaxWidth(), edgePadding = 0.dp, ) { - listTopic.forEachIndexed { index, tab -> + listTag.forEachIndexed { index, tag -> Tab( selected = selectedTabIndex == index, selectedContentColor = MaterialTheme.colorScheme.primary, @@ -96,7 +93,7 @@ fun PostsPage( pagerState.animateScrollToPage(index) } }, - text = { Text(text = tab) }, + text = { Text(text = tag.name) }, ) } } @@ -105,11 +102,13 @@ fun PostsPage( state = pagerState, modifier = Modifier.fillMaxSize(), ) { pageIndex -> - val page = listTopic[pageIndex] + val tag = listTag[pageIndex] + PageContent( appViewModel, siteConfig, - page, + tag = tag, + navToCustomTag = navToCustomTag, onPageChange = { currentPage, total -> pageProgress = currentPage to total }) { post -> @@ -124,14 +123,15 @@ fun PostsPage( fun PageContent( appViewModel: AppViewModel, siteConfig: SiteConfig, - topic: String, + tag: Tag, onPageChange: (Int, Int) -> Unit, + navToCustomTag: (Tag) -> Unit = {}, onClick: (PostItem) -> Unit ) { val listFavorite by appViewModel.favoritePosts.collectAsState(initial = emptyList()) val viewModel: PostsViewModel = viewModel( - factory = PostsViewModelFactory(siteConfig, topic), - key = topic + factory = PostsViewModelFactory(siteConfig, tag), + key = tag.name ) val listState = rememberLazyListState() val uiState by viewModel.stateFlow.collectAsState() @@ -164,6 +164,9 @@ fun PageContent( PostCard( post, isFavorite = listFavorite.find { it.url == post.url } != null, + onTagClick = { + navToCustomTag(it) + }, setFavorite = { isFavorite -> if (isFavorite) appViewModel.addFavorite(post) @@ -191,41 +194,3 @@ fun PageContent( HGoTop(listState) } } - -@Composable -fun PostCard( - postItem: PostItem, - isFavorite: Boolean = false, - setFavorite: (Boolean) -> Unit = {}, - viewPost: () -> Unit -) { - Card( - elevation = CardDefaults.cardElevation(4.dp), - modifier = Modifier - .padding(horizontal = 16.dp, vertical = 12.dp), - border = CardDefaults.outlinedCardBorder(), - shape = CardDefaults.outlinedShape, - onClick = { viewPost() }, - ) { - Box( - modifier = Modifier - .fillMaxSize() - .animateContentSize( - animationSpec = tween(durationMillis = 300) - ), - ) { - Column(modifier = Modifier.padding(8.dp)) { - HImage( - url = postItem.thumbnail - ) - Text(postItem.name, fontSize = 12.sp) - } - HFavoriteIcon( - modifier = Modifier.align(Alignment.TopEnd), - isFavorite = isFavorite - ) { - setFavorite(!isFavorite) - } - } - } -} \ No newline at end of file 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 617a2d3..400f307 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 @@ -7,14 +7,14 @@ import com.paulcoding.hviewer.js.JS import com.paulcoding.hviewer.model.PostItem import com.paulcoding.hviewer.model.Posts import com.paulcoding.hviewer.model.SiteConfig +import com.paulcoding.hviewer.model.Tag import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -class PostsViewModel(siteConfig: SiteConfig, topic: String) : ViewModel() { - private val topicUrl = siteConfig.tags[topic] ?: "" - +class PostsViewModel(siteConfig: SiteConfig, tag: Tag) : ViewModel() { + private val topicUrl = tag.url private var _stateFlow = MutableStateFlow(UiState()) val stateFlow = _stateFlow.asStateFlow() @@ -75,11 +75,11 @@ class PostsViewModel(siteConfig: SiteConfig, topic: String) : ViewModel() { } @Suppress("UNCHECKED_CAST") -class PostsViewModelFactory(private val siteConfig: SiteConfig, private val topic: String) : +class PostsViewModelFactory(private val siteConfig: SiteConfig, private val tag: Tag) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { if (modelClass.isAssignableFrom(PostsViewModel::class.java)) { - return PostsViewModel(siteConfig, topic) as T + return PostsViewModel(siteConfig, tag) as T } throw IllegalArgumentException("Unknown ViewModel class") } diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/search/SearchPage.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/search/SearchPage.kt index d92801c..5062c65 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/search/SearchPage.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/search/SearchPage.kt @@ -38,6 +38,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.paulcoding.hviewer.MainApp.Companion.appContext import com.paulcoding.hviewer.extensions.isScrolledToEnd import com.paulcoding.hviewer.model.PostItem +import com.paulcoding.hviewer.model.Tag import com.paulcoding.hviewer.ui.component.HBackIcon import com.paulcoding.hviewer.ui.component.HEmpty import com.paulcoding.hviewer.ui.component.HGoTop @@ -53,6 +54,7 @@ import com.paulcoding.hviewer.ui.page.posts.PostCard fun SearchPage( appViewModel: AppViewModel, navToImages: (post: PostItem) -> Unit, + navToCustomTag: (tag: Tag) -> Unit, goBack: () -> Unit, ) { val appState by appViewModel.stateFlow.collectAsState() @@ -111,7 +113,11 @@ fun SearchPage( ) HIcon(Icons.Outlined.Search) { submit() } } - PageContent(appViewModel = appViewModel, viewModel = viewModel) { post -> + PageContent( + appViewModel = appViewModel, + navToCustomTag = navToCustomTag, + viewModel = viewModel + ) { post -> navToImages(post) } } @@ -123,6 +129,7 @@ fun SearchPage( fun PageContent( appViewModel: AppViewModel, viewModel: SearchViewModel, + navToCustomTag: (Tag) -> Unit, onClick: (PostItem) -> Unit ) { val listState = rememberLazyListState() @@ -151,8 +158,12 @@ fun PageContent( appViewModel.addFavorite(post) else appViewModel.deleteFavorite(post) + }, + onTagClick = { + navToCustomTag(it) } - ) { + ) + { onClick(post) } }