Decentralize database
This commit is contained in:
@@ -45,7 +45,6 @@ import okhttp3.Protocol
|
|||||||
import org.kodein.di.*
|
import org.kodein.di.*
|
||||||
import org.kodein.di.android.x.androidXModule
|
import org.kodein.di.android.x.androidXModule
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.pupil.db.databaseModule
|
|
||||||
import xyz.quaver.pupil.sources.sourceModule
|
import xyz.quaver.pupil.sources.sourceModule
|
||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -54,7 +53,6 @@ class Pupil : Application(), DIAware {
|
|||||||
|
|
||||||
override val di: DI by DI.lazy {
|
override val di: DI by DI.lazy {
|
||||||
import(androidXModule(this@Pupil))
|
import(androidXModule(this@Pupil))
|
||||||
import(databaseModule)
|
|
||||||
import(sourceModule)
|
import(sourceModule)
|
||||||
|
|
||||||
bind { singleton { NetworkCache(applicationContext) } }
|
bind { singleton { NetworkCache(applicationContext) } }
|
||||||
|
|||||||
@@ -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))
|
|
||||||
}
|
|
||||||
@@ -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() }
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -20,12 +20,8 @@ package xyz.quaver.pupil.sources.composable
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Parcelable
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import androidx.compose.animation.core.*
|
import androidx.compose.animation.core.*
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.layout.*
|
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.Offset
|
||||||
import androidx.compose.ui.geometry.Rect
|
import androidx.compose.ui.geometry.Rect
|
||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.input.pointer.pointerInteropFilter
|
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
@@ -79,25 +73,22 @@ import kotlinx.coroutines.flow.takeWhile
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import org.kodein.di.DIAware
|
import org.kodein.di.DIAware
|
||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
import org.kodein.log.LoggerFactory
|
import org.kodein.log.LoggerFactory
|
||||||
import org.kodein.log.newLogger
|
import org.kodein.log.newLogger
|
||||||
import xyz.quaver.graphics.subsampledimage.*
|
import xyz.quaver.graphics.subsampledimage.ImageSource
|
||||||
import xyz.quaver.graphics.subsampledimage.ScaleTypes.CENTER_INSIDE
|
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.io.FileX
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.db.AppDatabase
|
|
||||||
import xyz.quaver.pupil.proto.ReaderOptions
|
import xyz.quaver.pupil.proto.ReaderOptions
|
||||||
import xyz.quaver.pupil.proto.settingsDataStore
|
import xyz.quaver.pupil.proto.settingsDataStore
|
||||||
import xyz.quaver.pupil.ui.theme.Orange500
|
|
||||||
import xyz.quaver.pupil.util.FileXImageSource
|
import xyz.quaver.pupil.util.FileXImageSource
|
||||||
import xyz.quaver.pupil.util.NetworkCache
|
import xyz.quaver.pupil.util.NetworkCache
|
||||||
import xyz.quaver.pupil.util.activity
|
import xyz.quaver.pupil.util.activity
|
||||||
import xyz.quaver.pupil.util.rememberFileXImageSource
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.sign
|
import kotlin.math.sign
|
||||||
@@ -189,8 +180,6 @@ open class ReaderBaseViewModel(app: Application) : AndroidViewModel(app), DIAwar
|
|||||||
|
|
||||||
var fullscreen by mutableStateOf(false)
|
var fullscreen by mutableStateOf(false)
|
||||||
|
|
||||||
private val database: AppDatabase by instance()
|
|
||||||
|
|
||||||
var error by mutableStateOf(false)
|
var error by mutableStateOf(false)
|
||||||
|
|
||||||
var imageCount by mutableStateOf(0)
|
var imageCount by mutableStateOf(0)
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import androidx.compose.ui.unit.Velocity
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastFirstOrNull
|
import androidx.compose.ui.util.fastFirstOrNull
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||||
import com.google.accompanist.insets.LocalWindowInsets
|
import com.google.accompanist.insets.LocalWindowInsets
|
||||||
@@ -67,7 +68,7 @@ private enum class NavigationIconState {
|
|||||||
ARROW
|
ARROW
|
||||||
}
|
}
|
||||||
|
|
||||||
open class SearchBaseViewModel<T>(app: Application) : AndroidViewModel(app) {
|
open class SearchBaseViewModel<T> : ViewModel() {
|
||||||
val searchResults = mutableStateListOf<T>()
|
val searchResults = mutableStateListOf<T>()
|
||||||
|
|
||||||
var sortModeIndex by mutableStateOf(0)
|
var sortModeIndex by mutableStateOf(0)
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -33,7 +33,6 @@ import androidx.compose.material.*
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -47,20 +46,22 @@ import androidx.navigation.NavController
|
|||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.navigation
|
import androidx.navigation.compose.navigation
|
||||||
|
import androidx.room.Room
|
||||||
import com.google.accompanist.insets.LocalWindowInsets
|
import com.google.accompanist.insets.LocalWindowInsets
|
||||||
import com.google.accompanist.insets.rememberInsetsPaddingValues
|
import com.google.accompanist.insets.rememberInsetsPaddingValues
|
||||||
import com.google.accompanist.insets.ui.Scaffold
|
import com.google.accompanist.insets.ui.Scaffold
|
||||||
import com.google.accompanist.insets.ui.TopAppBar
|
import com.google.accompanist.insets.ui.TopAppBar
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.kodein.di.DIAware
|
import org.kodein.di.*
|
||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
|
import org.kodein.di.android.subDI
|
||||||
import org.kodein.di.compose.rememberInstance
|
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.LoggerFactory
|
||||||
import org.kodein.log.newLogger
|
import org.kodein.log.newLogger
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.db.AppDatabase
|
|
||||||
import xyz.quaver.pupil.proto.settingsDataStore
|
import xyz.quaver.pupil.proto.settingsDataStore
|
||||||
import xyz.quaver.pupil.sources.Source
|
import xyz.quaver.pupil.sources.Source
|
||||||
import xyz.quaver.pupil.sources.composable.*
|
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.getReferer
|
||||||
import xyz.quaver.pupil.sources.hitomi.lib.imageUrlFromImage
|
import xyz.quaver.pupil.sources.hitomi.lib.imageUrlFromImage
|
||||||
import xyz.quaver.pupil.ui.theme.Orange500
|
import xyz.quaver.pupil.ui.theme.Orange500
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
|
||||||
class Hitomi(app: Application) : Source(), DIAware {
|
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 client: HttpClient by instance()
|
||||||
|
|
||||||
private val logger = newLogger(LoggerFactory.default)
|
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 name: String = "hitomi.la"
|
||||||
override val iconResID: Int = R.drawable.hitomi
|
override val iconResID: Int = R.drawable.hitomi
|
||||||
|
|
||||||
override fun NavGraphBuilder.navGraph(navController: NavController) {
|
override fun NavGraphBuilder.navGraph(navController: NavController) {
|
||||||
navigation(startDestination = "hitomi.la/search", route = name) {
|
navigation(startDestination = "hitomi.la/search", route = name) {
|
||||||
composable("hitomi.la/search") { Search(navController) }
|
composable("hitomi.la/search") { withDI(di) { Search(navController) } }
|
||||||
composable("hitomi.la/reader/{itemID}") { Reader(navController) }
|
composable("hitomi.la/reader/{itemID}") { withDI(di) { Reader(navController) } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Search(navController: NavController) {
|
fun Search(navController: NavController) {
|
||||||
val model: HitomiSearchResultViewModel = viewModel()
|
val model: HitomiSearchResultViewModel by rememberViewModel()
|
||||||
val database: AppDatabase by rememberInstance()
|
val database: HitomiDatabase by rememberInstance()
|
||||||
val bookmarkDao = remember { database.bookmarkDao() }
|
val favoritesDao = remember { database.favoritesDao() }
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
val bookmarks by bookmarkDao.getAll(name).observeAsState()
|
val favorites by favoritesDao.getAll().collectAsState(emptyList())
|
||||||
val bookmarkSet by derivedStateOf {
|
val favoritesSet by derivedStateOf {
|
||||||
bookmarks?.toSet() ?: emptySet()
|
Collections.unmodifiableSet(favorites.mapTo(mutableSetOf()) { it.item })
|
||||||
}
|
}
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -200,11 +205,11 @@ class Hitomi(app: Application) : Source(), DIAware {
|
|||||||
items(model.searchResults) {
|
items(model.searchResults) {
|
||||||
DetailedSearchResult(
|
DetailedSearchResult(
|
||||||
it,
|
it,
|
||||||
bookmarks = bookmarkSet,
|
favorites = favoritesSet,
|
||||||
onBookmarkToggle = {
|
onFavoriteToggle = {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
if (it in bookmarkSet) bookmarkDao.delete(name, it)
|
if (it in favoritesSet) favoritesDao.delete(it)
|
||||||
else bookmarkDao.insert(name, it)
|
else favoritesDao.insert(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { result ->
|
) { result ->
|
||||||
@@ -219,8 +224,8 @@ class Hitomi(app: Application) : Source(), DIAware {
|
|||||||
fun Reader(navController: NavController) {
|
fun Reader(navController: NavController) {
|
||||||
val model: ReaderBaseViewModel = viewModel()
|
val model: ReaderBaseViewModel = viewModel()
|
||||||
|
|
||||||
val database: AppDatabase by rememberInstance()
|
val database: HitomiDatabase by rememberInstance()
|
||||||
val bookmarkDao = database.bookmarkDao()
|
val favoritesDao = remember { database.favoritesDao() }
|
||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
@@ -228,7 +233,7 @@ class Hitomi(app: Application) : Source(), DIAware {
|
|||||||
|
|
||||||
if (itemID == null) model.error = true
|
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) {
|
val galleryInfo by produceState<GalleryInfo?>(null) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val galleryID = itemID!!.toInt()
|
val galleryID = itemID!!.toInt()
|
||||||
@@ -271,13 +276,13 @@ class Hitomi(app: Application) : Source(), DIAware {
|
|||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
itemID?.let {
|
itemID?.let {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
if (bookmark) bookmarkDao.delete(name, it)
|
if (isFavorite) favoritesDao.delete(it)
|
||||||
else bookmarkDao.insert(name, it)
|
else favoritesDao.insert(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
if (bookmark) Icons.Default.Star else Icons.Default.StarOutline,
|
if (isFavorite) Icons.Default.Star else Icons.Default.StarOutline,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = Orange500
|
tint = Orange500
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.sources.hitomi
|
package xyz.quaver.pupil.sources.hitomi
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -29,12 +28,6 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.cancelAndJoin
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.yield
|
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.composable.SearchBaseViewModel
|
||||||
import xyz.quaver.pupil.sources.hitomi.lib.GalleryBlock
|
import xyz.quaver.pupil.sources.hitomi.lib.GalleryBlock
|
||||||
import xyz.quaver.pupil.sources.hitomi.lib.doSearch
|
import xyz.quaver.pupil.sources.hitomi.lib.doSearch
|
||||||
@@ -43,16 +36,9 @@ import kotlin.math.ceil
|
|||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class HitomiSearchResultViewModel(app: Application) : SearchBaseViewModel<HitomiSearchResult>(app), DIAware {
|
class HitomiSearchResultViewModel(
|
||||||
override val di by closestDI(app)
|
private val client: HttpClient
|
||||||
|
) : SearchBaseViewModel<HitomiSearchResult>() {
|
||||||
private val logger = newLogger(LoggerFactory.default)
|
|
||||||
|
|
||||||
private val client: HttpClient by instance()
|
|
||||||
|
|
||||||
private val database: AppDatabase by instance()
|
|
||||||
private val bookmarkDao = database.bookmarkDao()
|
|
||||||
|
|
||||||
private var cachedQuery: String? = null
|
private var cachedQuery: String? = null
|
||||||
private var cachedSortByPopularity: Boolean? = null
|
private var cachedSortByPopularity: Boolean? = null
|
||||||
private val cache = mutableListOf<Int>()
|
private val cache = mutableListOf<Int>()
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
package xyz.quaver.pupil.sources.hitomi.composable
|
package xyz.quaver.pupil.sources.hitomi.composable
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
@@ -101,8 +100,8 @@ private fun String.wordCapitalize() : String {
|
|||||||
@Composable
|
@Composable
|
||||||
fun DetailedSearchResult(
|
fun DetailedSearchResult(
|
||||||
result: HitomiSearchResult,
|
result: HitomiSearchResult,
|
||||||
bookmarks: Set<String>,
|
favorites: Set<String>,
|
||||||
onBookmarkToggle: (String) -> Unit = { },
|
onFavoriteToggle: (String) -> Unit = { },
|
||||||
onClick: (HitomiSearchResult) -> Unit = { }
|
onClick: (HitomiSearchResult) -> Unit = { }
|
||||||
) {
|
) {
|
||||||
val painter = rememberImagePainter(result.thumbnail)
|
val painter = rememberImagePainter(result.thumbnail)
|
||||||
@@ -169,8 +168,8 @@ fun DetailedSearchResult(
|
|||||||
key(result.tags) {
|
key(result.tags) {
|
||||||
TagGroup(
|
TagGroup(
|
||||||
tags = result.tags,
|
tags = result.tags,
|
||||||
bookmarks,
|
favorites,
|
||||||
onBookmarkToggle = onBookmarkToggle
|
onFavoriteToggle = onFavoriteToggle
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,13 +191,13 @@ fun DetailedSearchResult(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Icon(
|
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,
|
contentDescription = null,
|
||||||
tint = Orange500,
|
tint = Orange500,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(24.dp)
|
.size(24.dp)
|
||||||
.clickable {
|
.clickable {
|
||||||
onBookmarkToggle(result.itemID)
|
onFavoriteToggle(result.itemID)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -210,20 +209,20 @@ fun DetailedSearchResult(
|
|||||||
@Composable
|
@Composable
|
||||||
fun TagGroup(
|
fun TagGroup(
|
||||||
tags: List<String>,
|
tags: List<String>,
|
||||||
bookmarks: Set<String>,
|
favorites: Set<String>,
|
||||||
onBookmarkToggle: (String) -> Unit = { }
|
onFavoriteToggle: (String) -> Unit = { }
|
||||||
) {
|
) {
|
||||||
var isFolded by remember { mutableStateOf(true) }
|
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)) {
|
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 ->
|
.let { (if (isFolded) it.take(10) else it) }.forEach { tag ->
|
||||||
TagChip(
|
TagChip(
|
||||||
tag = tag,
|
tag = tag,
|
||||||
isFavorite = bookmarkedTagsInList.contains(tag),
|
isFavorite = favoriteTagsInList.contains(tag),
|
||||||
onFavoriteClick = onBookmarkToggle
|
onFavoriteClick = onFavoriteToggle
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -32,16 +32,27 @@ import androidx.navigation.NavController
|
|||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.navigation
|
import androidx.navigation.navigation
|
||||||
|
import androidx.room.Room
|
||||||
import org.kodein.di.DIAware
|
import org.kodein.di.DIAware
|
||||||
import org.kodein.di.android.closestDI
|
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.LoggerFactory
|
||||||
|
import org.kodein.log.frontend.defaultLogFrontend
|
||||||
import org.kodein.log.newLogger
|
import org.kodein.log.newLogger
|
||||||
|
import org.kodein.log.withShortPackageKeepLast
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.sources.Source
|
import xyz.quaver.pupil.sources.Source
|
||||||
import xyz.quaver.pupil.sources.manatoki.composable.Main
|
import xyz.quaver.pupil.sources.manatoki.composable.Main
|
||||||
import xyz.quaver.pupil.sources.manatoki.composable.Reader
|
import xyz.quaver.pupil.sources.manatoki.composable.Reader
|
||||||
import xyz.quaver.pupil.sources.manatoki.composable.Recent
|
import xyz.quaver.pupil.sources.manatoki.composable.Recent
|
||||||
import xyz.quaver.pupil.sources.manatoki.composable.Search
|
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(
|
@OptIn(
|
||||||
ExperimentalMaterialApi::class,
|
ExperimentalMaterialApi::class,
|
||||||
@@ -50,19 +61,27 @@ import xyz.quaver.pupil.sources.manatoki.composable.Search
|
|||||||
ExperimentalComposeUiApi::class
|
ExperimentalComposeUiApi::class
|
||||||
)
|
)
|
||||||
class Manatoki(app: Application) : Source(), DIAware {
|
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 name = "manatoki.net"
|
||||||
override val iconResID = R.drawable.manatoki
|
override val iconResID = R.drawable.manatoki
|
||||||
|
|
||||||
override fun NavGraphBuilder.navGraph(navController: NavController) {
|
override fun NavGraphBuilder.navGraph(navController: NavController) {
|
||||||
navigation(route = name, startDestination = "manatoki.net/") {
|
navigation(route = name, startDestination = "manatoki.net/") {
|
||||||
composable("manatoki.net/") { Main(navController) }
|
composable("manatoki.net/") { withDI(di) { Main(navController) } }
|
||||||
composable("manatoki.net/reader/{itemID}") { Reader(navController) }
|
composable("manatoki.net/reader/{itemID}") { withDI(di) { Reader(navController) } }
|
||||||
composable("manatoki.net/search") { Search(navController) }
|
composable("manatoki.net/search") { withDI(di) { Search(navController) } }
|
||||||
composable("manatoki.net/recent") { Recent(navController) }
|
composable("manatoki.net/recent") { withDI(di) { Recent(navController) } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.sources.manatoki.composable
|
package xyz.quaver.pupil.sources.manatoki.composable
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.layout.*
|
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.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.insets.LocalWindowInsets
|
import com.google.accompanist.insets.LocalWindowInsets
|
||||||
import com.google.accompanist.insets.navigationBarsPadding
|
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.Scaffold
|
||||||
import com.google.accompanist.insets.ui.TopAppBar
|
import com.google.accompanist.insets.ui.TopAppBar
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.kodein.di.compose.rememberInstance
|
import org.kodein.di.compose.rememberInstance
|
||||||
|
import org.kodein.di.compose.rememberViewModel
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.proto.settingsDataStore
|
import xyz.quaver.pupil.proto.settingsDataStore
|
||||||
import xyz.quaver.pupil.sources.composable.SourceSelectDialog
|
import xyz.quaver.pupil.sources.composable.SourceSelectDialog
|
||||||
@@ -62,7 +60,7 @@ import xyz.quaver.pupil.sources.manatoki.viewmodel.MainViewModel
|
|||||||
@ExperimentalMaterialApi
|
@ExperimentalMaterialApi
|
||||||
@Composable
|
@Composable
|
||||||
fun Main(navController: NavController) {
|
fun Main(navController: NavController) {
|
||||||
val model: MainViewModel = viewModel()
|
val model: MainViewModel by rememberViewModel()
|
||||||
|
|
||||||
val client: HttpClient by rememberInstance()
|
val client: HttpClient by rememberInstance()
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import androidx.compose.material.*
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -57,10 +56,11 @@ import io.ktor.client.*
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.kodein.di.compose.rememberInstance
|
import org.kodein.di.compose.rememberInstance
|
||||||
|
import org.kodein.di.compose.rememberViewModel
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.db.AppDatabase
|
|
||||||
import xyz.quaver.pupil.sources.composable.ReaderBase
|
import xyz.quaver.pupil.sources.composable.ReaderBase
|
||||||
import xyz.quaver.pupil.sources.composable.ReaderBaseViewModel
|
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.MangaListing
|
||||||
import xyz.quaver.pupil.sources.manatoki.ReaderInfo
|
import xyz.quaver.pupil.sources.manatoki.ReaderInfo
|
||||||
import xyz.quaver.pupil.sources.manatoki.getItem
|
import xyz.quaver.pupil.sources.manatoki.getItem
|
||||||
@@ -79,8 +79,9 @@ fun Reader(navController: NavController) {
|
|||||||
|
|
||||||
val client: HttpClient by rememberInstance()
|
val client: HttpClient by rememberInstance()
|
||||||
|
|
||||||
val database: AppDatabase by rememberInstance()
|
val database: ManatokiDatabase by rememberInstance()
|
||||||
val bookmarkDao = database.bookmarkDao()
|
val favoriteDao = remember { database.favoriteDao() }
|
||||||
|
val bookmarkDao = remember { database.bookmarkDao() }
|
||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
@@ -98,7 +99,7 @@ fun Reader(navController: NavController) {
|
|||||||
else model.error = true
|
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)
|
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
|
||||||
var mangaListing: MangaListing? by rememberSaveable { mutableStateOf(null) }
|
var mangaListing: MangaListing? by rememberSaveable { mutableStateOf(null) }
|
||||||
@@ -174,13 +175,13 @@ fun Reader(navController: NavController) {
|
|||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
itemID?.let {
|
itemID?.let {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
if (bookmark) bookmarkDao.delete("manatoki.net", it)
|
if (isFavorite) favoriteDao.delete(it)
|
||||||
else bookmarkDao.insert("manatoki.net", it)
|
else favoriteDao.insert(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
if (bookmark) Icons.Default.Star else Icons.Default.StarOutline,
|
if (isFavorite) Icons.Default.Star else Icons.Default.StarOutline,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = Orange500
|
tint = Orange500
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import com.google.accompanist.insets.ui.TopAppBar
|
|||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.kodein.di.compose.rememberInstance
|
import org.kodein.di.compose.rememberInstance
|
||||||
|
import org.kodein.di.compose.rememberViewModel
|
||||||
import xyz.quaver.pupil.sources.composable.OverscrollPager
|
import xyz.quaver.pupil.sources.composable.OverscrollPager
|
||||||
import xyz.quaver.pupil.sources.manatoki.MangaListing
|
import xyz.quaver.pupil.sources.manatoki.MangaListing
|
||||||
import xyz.quaver.pupil.sources.manatoki.getItem
|
import xyz.quaver.pupil.sources.manatoki.getItem
|
||||||
@@ -51,7 +52,7 @@ import xyz.quaver.pupil.sources.manatoki.viewmodel.RecentViewModel
|
|||||||
@ExperimentalMaterialApi
|
@ExperimentalMaterialApi
|
||||||
@Composable
|
@Composable
|
||||||
fun Recent(navController: NavController) {
|
fun Recent(navController: NavController) {
|
||||||
val model: RecentViewModel = viewModel()
|
val model: RecentViewModel by rememberViewModel()
|
||||||
|
|
||||||
val client: HttpClient by rememberInstance()
|
val client: HttpClient by rememberInstance()
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import com.google.accompanist.insets.ui.TopAppBar
|
|||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.kodein.di.compose.rememberInstance
|
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.ModalTopSheetLayout
|
||||||
import xyz.quaver.pupil.sources.composable.ModalTopSheetState
|
import xyz.quaver.pupil.sources.composable.ModalTopSheetState
|
||||||
import xyz.quaver.pupil.sources.composable.OverscrollPager
|
import xyz.quaver.pupil.sources.composable.OverscrollPager
|
||||||
@@ -70,7 +71,7 @@ import xyz.quaver.pupil.sources.manatoki.viewmodel.*
|
|||||||
@ExperimentalMaterialApi
|
@ExperimentalMaterialApi
|
||||||
@Composable
|
@Composable
|
||||||
fun Search(navController: NavController) {
|
fun Search(navController: NavController) {
|
||||||
val model: SearchViewModel = viewModel()
|
val model: SearchViewModel by rememberViewModel()
|
||||||
|
|
||||||
val client: HttpClient by rememberInstance()
|
val client: HttpClient by rememberInstance()
|
||||||
|
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ suspend fun HttpClient.getItem(
|
|||||||
}.toString()
|
}.toString()
|
||||||
|
|
||||||
val urls = Jsoup.parse(htmlData)
|
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 {
|
.map {
|
||||||
it.attributes()
|
it.attributes()
|
||||||
.first { it.key.startsWith("data-") }
|
.first { it.key.startsWith("data-") }
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package xyz.quaver.pupil.sources.manatoki.viewmodel
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
@@ -46,13 +47,9 @@ data class TopWeekly(
|
|||||||
val count: String
|
val count: String
|
||||||
)
|
)
|
||||||
|
|
||||||
class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
|
class MainViewModel(
|
||||||
override val di by closestDI(app)
|
private val client: HttpClient
|
||||||
|
) : ViewModel() {
|
||||||
private val logger = newLogger(LoggerFactory.default)
|
|
||||||
|
|
||||||
private val client: HttpClient by instance()
|
|
||||||
|
|
||||||
val recentUpload = mutableStateListOf<Thumbnail>()
|
val recentUpload = mutableStateListOf<Thumbnail>()
|
||||||
val mangaList = mutableStateListOf<Thumbnail>()
|
val mangaList = mutableStateListOf<Thumbnail>()
|
||||||
val topWeekly = mutableStateListOf<TopWeekly>()
|
val topWeekly = mutableStateListOf<TopWeekly>()
|
||||||
@@ -109,7 +106,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
|
|||||||
topWeekly.add(TopWeekly(itemID, title, count))
|
topWeekly.add(TopWeekly(itemID, title, count))
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
logger.warning(it)
|
TODO()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import androidx.compose.runtime.mutableStateListOf
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.request.*
|
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.composable.Thumbnail
|
||||||
import xyz.quaver.pupil.sources.manatoki.manatokiUrl
|
import xyz.quaver.pupil.sources.manatoki.manatokiUrl
|
||||||
|
|
||||||
class RecentViewModel(app: Application): AndroidViewModel(app), DIAware {
|
class RecentViewModel(
|
||||||
override val di by closestDI(app)
|
private val client: HttpClient
|
||||||
|
): ViewModel() {
|
||||||
private val client: HttpClient by instance()
|
|
||||||
|
|
||||||
var page by mutableStateOf(1)
|
var page by mutableStateOf(1)
|
||||||
|
|
||||||
var loading by mutableStateOf(false)
|
var loading by mutableStateOf(false)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.os.Parcelable
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -115,13 +116,11 @@ val availableSst = mapOf(
|
|||||||
"as_bookmark" to "북마크순"
|
"as_bookmark" to "북마크순"
|
||||||
)
|
)
|
||||||
|
|
||||||
class SearchViewModel(app: Application) : AndroidViewModel(app), DIAware {
|
class SearchViewModel(
|
||||||
override val di by closestDI(app)
|
private val client: HttpClient
|
||||||
|
) : ViewModel() {
|
||||||
private val logger = newLogger(LoggerFactory.default)
|
private val logger = newLogger(LoggerFactory.default)
|
||||||
|
|
||||||
private val client: HttpClient by instance()
|
|
||||||
|
|
||||||
// 발행
|
// 발행
|
||||||
var publish by mutableStateOf("")
|
var publish by mutableStateOf("")
|
||||||
// 초성
|
// 초성
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import xyz.quaver.graphics.subsampledimage.ImageSource
|
|||||||
import xyz.quaver.graphics.subsampledimage.newBitmapRegionDecoder
|
import xyz.quaver.graphics.subsampledimage.newBitmapRegionDecoder
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.io.util.inputStream
|
import xyz.quaver.io.util.inputStream
|
||||||
import xyz.quaver.pupil.db.AppDatabase
|
|
||||||
import xyz.quaver.pupil.sources.SourceEntries
|
import xyz.quaver.pupil.sources.SourceEntries
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
@@ -55,17 +54,6 @@ val JsonElement.content
|
|||||||
fun DIAware.source(source: String) = lazy { direct.source(source) }
|
fun DIAware.source(source: String) = lazy { direct.source(source) }
|
||||||
fun DirectDIAware.source(source: String) = instance<SourceEntries>().toMap()[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 {
|
class FileXImageSource(val file: FileX): ImageSource {
|
||||||
private val decoder by lazy {
|
private val decoder by lazy {
|
||||||
file.inputStream()!!.use {
|
file.inputStream()!!.use {
|
||||||
|
|||||||
Reference in New Issue
Block a user