Decentralize database

This commit is contained in:
tom5079
2021-12-27 11:57:26 +09:00
parent c3c5761ffa
commit 850ac3ea83
21 changed files with 226 additions and 202 deletions

View File

@@ -45,7 +45,6 @@ import okhttp3.Protocol
import org.kodein.di.*
import org.kodein.di.android.x.androidXModule
import xyz.quaver.io.FileX
import xyz.quaver.pupil.db.databaseModule
import xyz.quaver.pupil.sources.sourceModule
import xyz.quaver.pupil.util.*
import java.util.*
@@ -54,7 +53,6 @@ class Pupil : Application(), DIAware {
override val di: DI by DI.lazy {
import(androidXModule(this@Pupil))
import(databaseModule)
import(sourceModule)
bind { singleton { NetworkCache(applicationContext) } }

View File

@@ -1,34 +0,0 @@
package xyz.quaver.pupil.db
import androidx.lifecycle.LiveData
import androidx.room.*
@Entity(primaryKeys = ["source", "itemID"])
data class Bookmark(
val source: String,
val itemID: String,
val timestamp: Long = System.currentTimeMillis()
)
@Dao
interface BookmarkDao {
@Query("SELECT * FROM bookmark")
fun getAll(): LiveData<List<Bookmark>>
@Query("SELECT itemID FROM bookmark WHERE source = :source")
fun getAll(source: String): LiveData<List<String>>
@Query("SELECT EXISTS(SELECT * FROM bookmark WHERE source = :source AND itemID = :itemID)")
fun contains(source: String, itemID: String): LiveData<Boolean>
fun contains(bookmark: Bookmark) = contains(bookmark.source, bookmark.itemID)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(bookmark: Bookmark)
suspend fun insert(source: String, itemID: String) = insert(Bookmark(source, itemID))
@Delete
suspend fun delete(bookmark: Bookmark)
suspend fun delete(source: String, itemID: String) = delete(Bookmark(source, itemID))
}

View File

@@ -1,17 +0,0 @@
package xyz.quaver.pupil.db
import android.app.Application
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import org.kodein.di.*
@Database(entities = [History::class, Bookmark::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun historyDao(): HistoryDao
abstract fun bookmarkDao(): BookmarkDao
}
val databaseModule = DI.Module("database") {
bind<AppDatabase>() with singleton { Room.databaseBuilder(instance<Application>(), AppDatabase::class.java, "pupil").build() }
}

View File

@@ -1,26 +0,0 @@
package xyz.quaver.pupil.db
import androidx.lifecycle.LiveData
import androidx.room.*
@Entity(primaryKeys = ["source", "itemID"])
data class History(
val source: String,
val itemID: String,
val timestamp: Long = System.currentTimeMillis()
)
@Dao
interface HistoryDao {
@Query("SELECT * FROM history")
fun getAll(): LiveData<List<History>>
@Query("SELECT itemID FROM history WHERE source = :source")
fun getAll(source: String): LiveData<List<String>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(history: History)
@Delete
suspend fun delete(history: History)
}

View File

@@ -20,12 +20,8 @@ package xyz.quaver.pupil.sources.composable
import android.app.Application
import android.net.Uri
import android.os.Parcelable
import android.util.Log
import android.view.MotionEvent
import androidx.compose.animation.core.*
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
@@ -46,13 +42,11 @@ import androidx.compose.ui.draw.rotate
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
@@ -79,25 +73,22 @@ import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
import org.kodein.log.LoggerFactory
import org.kodein.log.newLogger
import xyz.quaver.graphics.subsampledimage.*
import xyz.quaver.graphics.subsampledimage.ScaleTypes.CENTER_INSIDE
import xyz.quaver.graphics.subsampledimage.ImageSource
import xyz.quaver.graphics.subsampledimage.SubSampledImage
import xyz.quaver.graphics.subsampledimage.SubSampledImageState
import xyz.quaver.graphics.subsampledimage.rememberSubSampledImageState
import xyz.quaver.io.FileX
import xyz.quaver.pupil.R
import xyz.quaver.pupil.db.AppDatabase
import xyz.quaver.pupil.proto.ReaderOptions
import xyz.quaver.pupil.proto.settingsDataStore
import xyz.quaver.pupil.ui.theme.Orange500
import xyz.quaver.pupil.util.FileXImageSource
import xyz.quaver.pupil.util.NetworkCache
import xyz.quaver.pupil.util.activity
import xyz.quaver.pupil.util.rememberFileXImageSource
import java.util.concurrent.ConcurrentHashMap
import kotlin.math.abs
import kotlin.math.sign
@@ -189,8 +180,6 @@ open class ReaderBaseViewModel(app: Application) : AndroidViewModel(app), DIAwar
var fullscreen by mutableStateOf(false)
private val database: AppDatabase by instance()
var error by mutableStateOf(false)
var imageCount by mutableStateOf(0)

View File

@@ -51,6 +51,7 @@ import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastFirstOrNull
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.drawablepainter.rememberDrawablePainter
import com.google.accompanist.insets.LocalWindowInsets
@@ -67,7 +68,7 @@ private enum class NavigationIconState {
ARROW
}
open class SearchBaseViewModel<T>(app: Application) : AndroidViewModel(app) {
open class SearchBaseViewModel<T> : ViewModel() {
val searchResults = mutableStateListOf<T>()
var sortModeIndex by mutableStateOf(0)

View File

@@ -0,0 +1,50 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2021 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.sources.hitomi
import androidx.room.*
import kotlinx.coroutines.flow.Flow
@Entity
data class Favorite(
@PrimaryKey val item: String,
val timestamp: Long = System.currentTimeMillis()
)
@Dao
interface FavoritesDao {
@Query("SELECT * FROM favorite")
fun getAll(): Flow<List<Favorite>>
@Query("SELECT EXISTS(SELECT * FROM favorite WHERE item = :item)")
fun contains(item: String): Flow<Boolean>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(favorite: Favorite)
suspend fun insert(item: String) = insert(Favorite(item))
@Delete
suspend fun delete(favorite: Favorite)
suspend fun delete(item: String) = delete(Favorite(item))
}
@Database(entities = [Favorite::class], version = 1)
abstract class HitomiDatabase : RoomDatabase() {
abstract fun favoritesDao(): FavoritesDao
}

View File

@@ -33,7 +33,6 @@ import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@@ -47,20 +46,22 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.compose.navigation
import androidx.room.Room
import com.google.accompanist.insets.LocalWindowInsets
import com.google.accompanist.insets.rememberInsetsPaddingValues
import com.google.accompanist.insets.ui.Scaffold
import com.google.accompanist.insets.ui.TopAppBar
import io.ktor.client.*
import kotlinx.coroutines.launch
import org.kodein.di.DIAware
import org.kodein.di.*
import org.kodein.di.android.closestDI
import org.kodein.di.android.subDI
import org.kodein.di.compose.rememberInstance
import org.kodein.di.instance
import org.kodein.di.compose.rememberViewModel
import org.kodein.di.compose.withDI
import org.kodein.log.LoggerFactory
import org.kodein.log.newLogger
import xyz.quaver.pupil.R
import xyz.quaver.pupil.db.AppDatabase
import xyz.quaver.pupil.proto.settingsDataStore
import xyz.quaver.pupil.sources.Source
import xyz.quaver.pupil.sources.composable.*
@@ -70,38 +71,42 @@ import xyz.quaver.pupil.sources.hitomi.lib.getGalleryInfo
import xyz.quaver.pupil.sources.hitomi.lib.getReferer
import xyz.quaver.pupil.sources.hitomi.lib.imageUrlFromImage
import xyz.quaver.pupil.ui.theme.Orange500
import java.util.*
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
class Hitomi(app: Application) : Source(), DIAware {
override val di by closestDI(app)
override val di by subDI(closestDI(app)) {
bindSingleton {
Room.databaseBuilder(app, HitomiDatabase::class.java, name).build()
}
bindProvider { HitomiSearchResultViewModel(instance()) }
}
private val client: HttpClient by instance()
private val logger = newLogger(LoggerFactory.default)
private val database: AppDatabase by instance()
private val bookmarkDao = database.bookmarkDao()
override val name: String = "hitomi.la"
override val iconResID: Int = R.drawable.hitomi
override fun NavGraphBuilder.navGraph(navController: NavController) {
navigation(startDestination = "hitomi.la/search", route = name) {
composable("hitomi.la/search") { Search(navController) }
composable("hitomi.la/reader/{itemID}") { Reader(navController) }
composable("hitomi.la/search") { withDI(di) { Search(navController) } }
composable("hitomi.la/reader/{itemID}") { withDI(di) { Reader(navController) } }
}
}
@Composable
fun Search(navController: NavController) {
val model: HitomiSearchResultViewModel = viewModel()
val database: AppDatabase by rememberInstance()
val bookmarkDao = remember { database.bookmarkDao() }
val model: HitomiSearchResultViewModel by rememberViewModel()
val database: HitomiDatabase by rememberInstance()
val favoritesDao = remember { database.favoritesDao() }
val coroutineScope = rememberCoroutineScope()
val bookmarks by bookmarkDao.getAll(name).observeAsState()
val bookmarkSet by derivedStateOf {
bookmarks?.toSet() ?: emptySet()
val favorites by favoritesDao.getAll().collectAsState(emptyList())
val favoritesSet by derivedStateOf {
Collections.unmodifiableSet(favorites.mapTo(mutableSetOf()) { it.item })
}
val context = LocalContext.current
@@ -200,11 +205,11 @@ class Hitomi(app: Application) : Source(), DIAware {
items(model.searchResults) {
DetailedSearchResult(
it,
bookmarks = bookmarkSet,
onBookmarkToggle = {
favorites = favoritesSet,
onFavoriteToggle = {
coroutineScope.launch {
if (it in bookmarkSet) bookmarkDao.delete(name, it)
else bookmarkDao.insert(name, it)
if (it in favoritesSet) favoritesDao.delete(it)
else favoritesDao.insert(it)
}
}
) { result ->
@@ -219,8 +224,8 @@ class Hitomi(app: Application) : Source(), DIAware {
fun Reader(navController: NavController) {
val model: ReaderBaseViewModel = viewModel()
val database: AppDatabase by rememberInstance()
val bookmarkDao = database.bookmarkDao()
val database: HitomiDatabase by rememberInstance()
val favoritesDao = remember { database.favoritesDao() }
val coroutineScope = rememberCoroutineScope()
@@ -228,7 +233,7 @@ class Hitomi(app: Application) : Source(), DIAware {
if (itemID == null) model.error = true
val bookmark by bookmarkDao.contains(name, itemID ?: "").observeAsState(false)
val isFavorite by favoritesDao.contains(itemID ?: "").collectAsState(false)
val galleryInfo by produceState<GalleryInfo?>(null) {
runCatching {
val galleryID = itemID!!.toInt()
@@ -271,13 +276,13 @@ class Hitomi(app: Application) : Source(), DIAware {
IconButton(onClick = {
itemID?.let {
coroutineScope.launch {
if (bookmark) bookmarkDao.delete(name, it)
else bookmarkDao.insert(name, it)
if (isFavorite) favoritesDao.delete(it)
else favoritesDao.insert(it)
}
}
}) {
Icon(
if (bookmark) Icons.Default.Star else Icons.Default.StarOutline,
if (isFavorite) Icons.Default.Star else Icons.Default.StarOutline,
contentDescription = null,
tint = Orange500
)

View File

@@ -18,7 +18,6 @@
package xyz.quaver.pupil.sources.hitomi
import android.app.Application
import android.util.LruCache
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -29,12 +28,6 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
import org.kodein.log.LoggerFactory
import org.kodein.log.newLogger
import xyz.quaver.pupil.db.AppDatabase
import xyz.quaver.pupil.sources.composable.SearchBaseViewModel
import xyz.quaver.pupil.sources.hitomi.lib.GalleryBlock
import xyz.quaver.pupil.sources.hitomi.lib.doSearch
@@ -43,16 +36,9 @@ import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
class HitomiSearchResultViewModel(app: Application) : SearchBaseViewModel<HitomiSearchResult>(app), DIAware {
override val di by closestDI(app)
private val logger = newLogger(LoggerFactory.default)
private val client: HttpClient by instance()
private val database: AppDatabase by instance()
private val bookmarkDao = database.bookmarkDao()
class HitomiSearchResultViewModel(
private val client: HttpClient
) : SearchBaseViewModel<HitomiSearchResult>() {
private var cachedQuery: String? = null
private var cachedSortByPopularity: Boolean? = null
private val cache = mutableListOf<Int>()

View File

@@ -19,7 +19,6 @@
package xyz.quaver.pupil.sources.hitomi.composable
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
@@ -101,8 +100,8 @@ private fun String.wordCapitalize() : String {
@Composable
fun DetailedSearchResult(
result: HitomiSearchResult,
bookmarks: Set<String>,
onBookmarkToggle: (String) -> Unit = { },
favorites: Set<String>,
onFavoriteToggle: (String) -> Unit = { },
onClick: (HitomiSearchResult) -> Unit = { }
) {
val painter = rememberImagePainter(result.thumbnail)
@@ -169,8 +168,8 @@ fun DetailedSearchResult(
key(result.tags) {
TagGroup(
tags = result.tags,
bookmarks,
onBookmarkToggle = onBookmarkToggle
favorites,
onFavoriteToggle = onFavoriteToggle
)
}
}
@@ -192,13 +191,13 @@ fun DetailedSearchResult(
)
Icon(
if (result.itemID in bookmarks) Icons.Default.Star else Icons.Default.StarOutline,
if (result.itemID in favorites) Icons.Default.Star else Icons.Default.StarOutline,
contentDescription = null,
tint = Orange500,
modifier = Modifier
.size(24.dp)
.clickable {
onBookmarkToggle(result.itemID)
onFavoriteToggle(result.itemID)
}
)
}
@@ -210,20 +209,20 @@ fun DetailedSearchResult(
@Composable
fun TagGroup(
tags: List<String>,
bookmarks: Set<String>,
onBookmarkToggle: (String) -> Unit = { }
favorites: Set<String>,
onFavoriteToggle: (String) -> Unit = { }
) {
var isFolded by remember { mutableStateOf(true) }
val bookmarkedTagsInList = bookmarks intersect tags.toSet()
val favoriteTagsInList = favorites intersect tags.toSet()
FlowRow(Modifier.padding(0.dp, 16.dp)) {
tags.sortedBy { if (bookmarkedTagsInList.contains(it)) 0 else 1 }
tags.sortedBy { if (favoriteTagsInList.contains(it)) 0 else 1 }
.let { (if (isFolded) it.take(10) else it) }.forEach { tag ->
TagChip(
tag = tag,
isFavorite = bookmarkedTagsInList.contains(tag),
onFavoriteClick = onBookmarkToggle
isFavorite = favoriteTagsInList.contains(tag),
onFavoriteClick = onFavoriteToggle
)
}

View File

@@ -0,0 +1,70 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2021 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.sources.manatoki
import androidx.room.*
import kotlinx.coroutines.flow.Flow
@Entity
data class Favorite(
@PrimaryKey val itemID: String
)
@Entity
data class Bookmark(
@PrimaryKey val itemID: String,
val page: Int
)
@Entity
data class History(
@PrimaryKey val itemID: String,
val page: Int
)
@Dao
interface FavoriteDao {
@Query("SELECT EXISTS(SELECT * FROM favorite WHERE itemID = :itemID)")
fun contains(itemID: String): Flow<Boolean>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(favorite: Favorite)
suspend fun insert(itemID: String) = insert(Favorite(itemID))
@Delete
suspend fun delete(favorite: Favorite)
suspend fun delete(itemID: String) = delete(Favorite(itemID))
}
@Dao
interface BookmarkDao {
}
@Dao
interface HistoryDao {
}
@Database(entities = [Favorite::class, Bookmark::class, History::class], version = 1)
abstract class ManatokiDatabase: RoomDatabase() {
abstract fun favoriteDao(): FavoriteDao
abstract fun bookmarkDao(): BookmarkDao
abstract fun historyDao(): HistoryDao
}

View File

@@ -32,16 +32,27 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.navigation
import androidx.room.Room
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.android.subDI
import org.kodein.di.bindProvider
import org.kodein.di.bindSingleton
import org.kodein.di.compose.withDI
import org.kodein.di.instance
import org.kodein.log.LoggerFactory
import org.kodein.log.frontend.defaultLogFrontend
import org.kodein.log.newLogger
import org.kodein.log.withShortPackageKeepLast
import xyz.quaver.pupil.R
import xyz.quaver.pupil.sources.Source
import xyz.quaver.pupil.sources.manatoki.composable.Main
import xyz.quaver.pupil.sources.manatoki.composable.Reader
import xyz.quaver.pupil.sources.manatoki.composable.Recent
import xyz.quaver.pupil.sources.manatoki.composable.Search
import xyz.quaver.pupil.sources.manatoki.viewmodel.MainViewModel
import xyz.quaver.pupil.sources.manatoki.viewmodel.RecentViewModel
import xyz.quaver.pupil.sources.manatoki.viewmodel.SearchViewModel
@OptIn(
ExperimentalMaterialApi::class,
@@ -50,19 +61,27 @@ import xyz.quaver.pupil.sources.manatoki.composable.Search
ExperimentalComposeUiApi::class
)
class Manatoki(app: Application) : Source(), DIAware {
override val di by closestDI(app)
override val di by subDI(closestDI(app)) {
bindSingleton {
Room.databaseBuilder(
app, ManatokiDatabase::class.java, name
).build()
}
private val logger = newLogger(LoggerFactory.default)
bindProvider { MainViewModel(instance()) }
bindProvider { RecentViewModel(instance()) }
bindProvider { SearchViewModel(instance()) }
}
override val name = "manatoki.net"
override val iconResID = R.drawable.manatoki
override fun NavGraphBuilder.navGraph(navController: NavController) {
navigation(route = name, startDestination = "manatoki.net/") {
composable("manatoki.net/") { Main(navController) }
composable("manatoki.net/reader/{itemID}") { Reader(navController) }
composable("manatoki.net/search") { Search(navController) }
composable("manatoki.net/recent") { Recent(navController) }
composable("manatoki.net/") { withDI(di) { Main(navController) } }
composable("manatoki.net/reader/{itemID}") { withDI(di) { Reader(navController) } }
composable("manatoki.net/search") { withDI(di) { Search(navController) } }
composable("manatoki.net/recent") { withDI(di) { Recent(navController) } }
}
}

View File

@@ -18,7 +18,6 @@
package xyz.quaver.pupil.sources.manatoki.composable
import android.util.Log
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
@@ -40,7 +39,6 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.google.accompanist.insets.LocalWindowInsets
import com.google.accompanist.insets.navigationBarsPadding
@@ -48,9 +46,9 @@ import com.google.accompanist.insets.rememberInsetsPaddingValues
import com.google.accompanist.insets.ui.Scaffold
import com.google.accompanist.insets.ui.TopAppBar
import io.ktor.client.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.kodein.di.compose.rememberInstance
import org.kodein.di.compose.rememberViewModel
import xyz.quaver.pupil.R
import xyz.quaver.pupil.proto.settingsDataStore
import xyz.quaver.pupil.sources.composable.SourceSelectDialog
@@ -62,7 +60,7 @@ import xyz.quaver.pupil.sources.manatoki.viewmodel.MainViewModel
@ExperimentalMaterialApi
@Composable
fun Main(navController: NavController) {
val model: MainViewModel = viewModel()
val model: MainViewModel by rememberViewModel()
val client: HttpClient by rememberInstance()

View File

@@ -34,7 +34,6 @@ import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@@ -57,10 +56,11 @@ import io.ktor.client.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.kodein.di.compose.rememberInstance
import org.kodein.di.compose.rememberViewModel
import xyz.quaver.pupil.R
import xyz.quaver.pupil.db.AppDatabase
import xyz.quaver.pupil.sources.composable.ReaderBase
import xyz.quaver.pupil.sources.composable.ReaderBaseViewModel
import xyz.quaver.pupil.sources.manatoki.ManatokiDatabase
import xyz.quaver.pupil.sources.manatoki.MangaListing
import xyz.quaver.pupil.sources.manatoki.ReaderInfo
import xyz.quaver.pupil.sources.manatoki.getItem
@@ -79,8 +79,9 @@ fun Reader(navController: NavController) {
val client: HttpClient by rememberInstance()
val database: AppDatabase by rememberInstance()
val bookmarkDao = database.bookmarkDao()
val database: ManatokiDatabase by rememberInstance()
val favoriteDao = remember { database.favoriteDao() }
val bookmarkDao = remember { database.bookmarkDao() }
val coroutineScope = rememberCoroutineScope()
@@ -98,7 +99,7 @@ fun Reader(navController: NavController) {
else model.error = true
}
val bookmark by bookmarkDao.contains("manatoki.net", itemID ?: "").observeAsState(false)
val isFavorite by favoriteDao.contains(itemID ?: "").collectAsState(false)
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
var mangaListing: MangaListing? by rememberSaveable { mutableStateOf(null) }
@@ -174,13 +175,13 @@ fun Reader(navController: NavController) {
IconButton(onClick = {
itemID?.let {
coroutineScope.launch {
if (bookmark) bookmarkDao.delete("manatoki.net", it)
else bookmarkDao.insert("manatoki.net", it)
if (isFavorite) favoriteDao.delete(it)
else favoriteDao.insert(it)
}
}
}) {
Icon(
if (bookmark) Icons.Default.Star else Icons.Default.StarOutline,
if (isFavorite) Icons.Default.Star else Icons.Default.StarOutline,
contentDescription = null,
tint = Orange500
)

View File

@@ -42,6 +42,7 @@ import com.google.accompanist.insets.ui.TopAppBar
import io.ktor.client.*
import kotlinx.coroutines.launch
import org.kodein.di.compose.rememberInstance
import org.kodein.di.compose.rememberViewModel
import xyz.quaver.pupil.sources.composable.OverscrollPager
import xyz.quaver.pupil.sources.manatoki.MangaListing
import xyz.quaver.pupil.sources.manatoki.getItem
@@ -51,7 +52,7 @@ import xyz.quaver.pupil.sources.manatoki.viewmodel.RecentViewModel
@ExperimentalMaterialApi
@Composable
fun Recent(navController: NavController) {
val model: RecentViewModel = viewModel()
val model: RecentViewModel by rememberViewModel()
val client: HttpClient by rememberInstance()

View File

@@ -58,6 +58,7 @@ import com.google.accompanist.insets.ui.TopAppBar
import io.ktor.client.*
import kotlinx.coroutines.launch
import org.kodein.di.compose.rememberInstance
import org.kodein.di.compose.rememberViewModel
import xyz.quaver.pupil.sources.composable.ModalTopSheetLayout
import xyz.quaver.pupil.sources.composable.ModalTopSheetState
import xyz.quaver.pupil.sources.composable.OverscrollPager
@@ -70,7 +71,7 @@ import xyz.quaver.pupil.sources.manatoki.viewmodel.*
@ExperimentalMaterialApi
@Composable
fun Search(navController: NavController) {
val model: SearchViewModel = viewModel()
val model: SearchViewModel by rememberViewModel()
val client: HttpClient by rememberInstance()

View File

@@ -144,7 +144,7 @@ suspend fun HttpClient.getItem(
}.toString()
val urls = Jsoup.parse(htmlData)
.select("img[^data-]:not([style]):not([src*=loading])").also { Log.d("PUPILD", it.size.toString()) }
.select("img[^data-]:not([style])")
.map {
it.attributes()
.first { it.key.startsWith("data-") }

View File

@@ -21,6 +21,7 @@ package xyz.quaver.pupil.sources.manatoki.viewmodel
import android.app.Application
import androidx.compose.runtime.mutableStateListOf
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.ktor.client.*
import io.ktor.client.request.*
@@ -46,13 +47,9 @@ data class TopWeekly(
val count: String
)
class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
override val di by closestDI(app)
private val logger = newLogger(LoggerFactory.default)
private val client: HttpClient by instance()
class MainViewModel(
private val client: HttpClient
) : ViewModel() {
val recentUpload = mutableStateListOf<Thumbnail>()
val mangaList = mutableStateListOf<Thumbnail>()
val topWeekly = mutableStateListOf<TopWeekly>()
@@ -109,7 +106,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
topWeekly.add(TopWeekly(itemID, title, count))
}
}.onFailure {
logger.warning(it)
TODO()
}
}
}

View File

@@ -24,6 +24,7 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.ktor.client.*
import io.ktor.client.request.*
@@ -37,11 +38,9 @@ import org.kodein.di.instance
import xyz.quaver.pupil.sources.manatoki.composable.Thumbnail
import xyz.quaver.pupil.sources.manatoki.manatokiUrl
class RecentViewModel(app: Application): AndroidViewModel(app), DIAware {
override val di by closestDI(app)
private val client: HttpClient by instance()
class RecentViewModel(
private val client: HttpClient
): ViewModel() {
var page by mutableStateOf(1)
var loading by mutableStateOf(false)

View File

@@ -23,6 +23,7 @@ import android.os.Parcelable
import android.util.Log
import androidx.compose.runtime.*
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import io.ktor.client.*
import io.ktor.client.request.*
import kotlinx.coroutines.Job
@@ -115,13 +116,11 @@ val availableSst = mapOf(
"as_bookmark" to "북마크순"
)
class SearchViewModel(app: Application) : AndroidViewModel(app), DIAware {
override val di by closestDI(app)
class SearchViewModel(
private val client: HttpClient
) : ViewModel() {
private val logger = newLogger(LoggerFactory.default)
private val client: HttpClient by instance()
// 발행
var publish by mutableStateOf("")
// 초성

View File

@@ -39,7 +39,6 @@ import xyz.quaver.graphics.subsampledimage.ImageSource
import xyz.quaver.graphics.subsampledimage.newBitmapRegionDecoder
import xyz.quaver.io.FileX
import xyz.quaver.io.util.inputStream
import xyz.quaver.pupil.db.AppDatabase
import xyz.quaver.pupil.sources.SourceEntries
import java.security.MessageDigest
@@ -55,17 +54,6 @@ val JsonElement.content
fun DIAware.source(source: String) = lazy { direct.source(source) }
fun DirectDIAware.source(source: String) = instance<SourceEntries>().toMap()[source]!!
fun DIAware.database() = lazy { direct.database() }
fun DirectDIAware.database() = instance<AppDatabase>()
fun View.hide() {
visibility = View.INVISIBLE
}
fun View.show() {
visibility = View.VISIBLE
}
class FileXImageSource(val file: FileX): ImageSource {
private val decoder by lazy {
file.inputStream()!!.use {