Prepare to export sources

This commit is contained in:
tom5079
2021-12-30 13:00:22 +09:00
parent 0e19d6c9b2
commit 2e11a4907a
14 changed files with 371 additions and 171 deletions

View File

@@ -20,7 +20,7 @@ android {
minSdk = 21
targetSdk = 31
versionCode = 600
versionName = "6.0.0-alpha1"
versionName = VERSION
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@@ -12,7 +12,7 @@
"filters": [],
"attributes": [],
"versionCode": 600,
"versionName": "6.0.0-alpha1",
"versionName": "6.0.0-alpha02",
"outputFile": "app-release.apk"
}
],

View File

@@ -243,7 +243,10 @@ open class ReaderBaseViewModel(app: Application) : AndroidViewModel(app), DIAwar
totalProgress++
}
}
else -> throw IllegalArgumentException("Expected URL scheme 'http(s)' or 'content' but was '$scheme'")
else -> {
logger.warning(IllegalArgumentException("Expected URL scheme 'http(s)' or 'content' but was '$scheme'"))
progressList[index] = Float.NEGATIVE_INFINITY
}
}
}
}

View File

@@ -44,7 +44,7 @@ interface FavoritesDao {
suspend fun delete(item: String) = delete(Favorite(item))
}
@Database(entities = [Favorite::class], version = 1)
@Database(entities = [Favorite::class], version = 1, exportSchema = false)
abstract class HitomiDatabase : RoomDatabase() {
abstract fun favoritesDao(): FavoritesDao
}

View File

@@ -20,6 +20,7 @@ package xyz.quaver.pupil.sources.manatoki
import androidx.room.*
import kotlinx.coroutines.flow.Flow
import java.sql.Timestamp
@Entity
data class Favorite(
@@ -35,7 +36,9 @@ data class Bookmark(
@Entity
data class History(
@PrimaryKey val itemID: String,
val page: Int
val parent: String,
val page: Int,
val timestamp: Long = System.currentTimeMillis()
)
@Dao
@@ -59,10 +62,21 @@ interface BookmarkDao {
@Dao
interface HistoryDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(history: History)
suspend fun insert(itemID: String, parent: String, page: Int) = insert(History(itemID, parent, page))
@Query("DELETE FROM history WHERE itemID = :itemID")
suspend fun delete(itemID: String)
@Query("SELECT parent FROM (SELECT parent, max(timestamp) as t FROM history GROUP BY parent) ORDER BY t DESC")
fun getRecentManga(): Flow<List<String>>
@Query("SELECT itemID FROM history WHERE parent = :parent ORDER BY timestamp DESC")
suspend fun getAll(parent: String): List<String>
}
@Database(entities = [Favorite::class, Bookmark::class, History::class], version = 1)
@Database(entities = [Favorite::class, Bookmark::class, History::class], version = 1, exportSchema = false)
abstract class ManatokiDatabase: RoomDatabase() {
abstract fun favoriteDao(): FavoriteDao
abstract fun bookmarkDao(): BookmarkDao

View File

@@ -20,9 +20,13 @@ package xyz.quaver.pupil.sources.manatoki.composable
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
@@ -33,8 +37,11 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
@@ -46,12 +53,15 @@ 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.isActive
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
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
@@ -64,15 +74,28 @@ fun Main(navController: NavController) {
val client: HttpClient by rememberInstance()
val database: ManatokiDatabase by rememberInstance()
val historyDao = remember { database.historyDao() }
val recent by remember { historyDao.getRecentManga() }.collectAsState(emptyList())
val recentManga = remember { mutableStateListOf<Thumbnail>() }
LaunchedEffect(recent) {
recentManga.clear()
recent.forEach {
if (isActive)
client.getItem(it, onListing = {
recentManga.add(
Thumbnail(it.itemID, it.title, it.thumbnail)
)
})
}
}
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
var mangaListing: MangaListing? by rememberSaveable { mutableStateOf(null) }
val coroutineScope = rememberCoroutineScope()
val onListing: (MangaListing) -> Unit = {
mangaListing = it
}
val context = LocalContext.current
LaunchedEffect(Unit) {
context.settingsDataStore.updateData {
@@ -82,13 +105,6 @@ fun Main(navController: NavController) {
}
}
val onReader: (ReaderInfo) -> Unit = { readerInfo ->
coroutineScope.launch {
sheetState.snapTo(ModalBottomSheetValue.Hidden)
navController.navigate("manatoki.net/reader/${readerInfo.itemID}")
}
}
var sourceSelectDialog by remember { mutableStateOf(false) }
if (sourceSelectDialog)
@@ -107,11 +123,86 @@ fun Main(navController: NavController) {
}
}
var mangaListing: MangaListing? by rememberSaveable { mutableStateOf(null) }
var recentItem: String? by rememberSaveable { mutableStateOf(null) }
val mangaListingListState = rememberLazyListState()
var mangaListingListSize: Size? by remember { mutableStateOf(null) }
val mangaListingInteractionSource = remember { mutableStateMapOf<String, MutableInteractionSource>() }
val navigationBarsPadding = LocalDensity.current.run {
rememberInsetsPaddingValues(
LocalWindowInsets.current.navigationBars
).calculateBottomPadding().toPx()
}
val onListing: (MangaListing) -> Unit = {
mangaListing = it
coroutineScope.launch {
val recentItemID = historyDao.getAll(it.itemID).firstOrNull() ?: return@launch
recentItem = recentItemID
while (mangaListingListState.layoutInfo.totalItemsCount != it.entries.size) {
delay(100)
}
val interactionSource = mangaListingInteractionSource.getOrPut(recentItemID) {
MutableInteractionSource()
}
val targetIndex =
it.entries.indexOfFirst { entry -> entry.itemID == recentItemID }
mangaListingListState.scrollToItem(targetIndex)
mangaListingListSize?.let { sheetSize ->
val targetItem =
mangaListingListState.layoutInfo.visibleItemsInfo.first {
it.key == recentItemID
}
if (targetItem.offset == 0) {
mangaListingListState.animateScrollBy(
-(sheetSize.height - navigationBarsPadding - targetItem.size)
)
}
delay(200)
with(interactionSource) {
val interaction =
PressInteraction.Press(
Offset(
sheetSize.width / 2,
targetItem.size / 2f
)
)
emit(interaction)
emit(PressInteraction.Release(interaction))
}
}
}
}
val onReader: (ReaderInfo) -> Unit = { readerInfo ->
coroutineScope.launch {
sheetState.snapTo(ModalBottomSheetValue.Hidden)
navController.navigate("manatoki.net/reader/${readerInfo.itemID}")
}
}
ModalBottomSheetLayout(
sheetState = sheetState,
sheetShape = RoundedCornerShape(32.dp, 32.dp, 0.dp, 0.dp),
sheetContent = {
MangaListingBottomSheet(mangaListing) {
MangaListingBottomSheet(
mangaListing,
onListSize = { mangaListingListSize = it },
rippleInteractionSource = mangaListingInteractionSource,
listState = mangaListingListState,
recentItem = recentItem
) {
coroutineScope.launch {
client.getItem(it, onListing, onReader)
}
@@ -164,6 +255,50 @@ fun Main(navController: NavController) {
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
if (recentManga.isNotEmpty()) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
"이어 보기",
style = MaterialTheme.typography.h5
)
IconButton(onClick = { navController.navigate("manatoki.net/recent") }) {
Icon(
Icons.Default.Add,
contentDescription = null
)
}
}
LazyRow(
modifier = Modifier
.fillMaxWidth()
.height(210.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
items(recentManga) { item ->
Thumbnail(
item,
Modifier
.width(180.dp)
.aspectRatio(6 / 7f)
) {
coroutineScope.launch {
mangaListing = null
sheetState.animateTo(ModalBottomSheetValue.Expanded)
}
coroutineScope.launch {
client.getItem(it, onListing, onReader)
}
}
}
}
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
@@ -195,7 +330,7 @@ fun Main(navController: NavController) {
.aspectRatio(6 / 7f)) {
coroutineScope.launch {
mangaListing = null
sheetState.show()
sheetState.animateTo(ModalBottomSheetValue.Expanded)
}
coroutineScope.launch {
client.getItem(it, onListing, onReader)
@@ -254,7 +389,7 @@ fun Main(navController: NavController) {
.aspectRatio(6f / 7)) {
coroutineScope.launch {
mangaListing = null
sheetState.show()
sheetState.animateTo(ModalBottomSheetValue.Expanded)
}
coroutineScope.launch {
client.getItem(it, onListing, onReader)
@@ -273,7 +408,7 @@ fun Main(navController: NavController) {
modifier = Modifier.clickable {
coroutineScope.launch {
mangaListing = null
sheetState.show()
sheetState.animateTo(ModalBottomSheetValue.Expanded)
}
coroutineScope.launch {

View File

@@ -18,14 +18,16 @@
package xyz.quaver.pupil.sources.manatoki.composable
import android.util.Log
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowRight
@@ -39,7 +41,6 @@ import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.SubcomposeLayout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import coil.compose.rememberImagePainter
@@ -50,7 +51,7 @@ import com.google.accompanist.insets.rememberInsetsPaddingValues
import xyz.quaver.pupil.sources.manatoki.MangaListing
private val FabSpacing = 8.dp
private val HeightPercentage = 75 // take 60% of the available space
private val HeightPercentage = 75 // take 75% of the available space
private enum class MangaListingBottomSheetLayoutContent { Top, Bottom, Fab }
@Composable
@@ -107,7 +108,9 @@ fun MangaListingBottomSheet(
currentItemID: String? = null,
onListSize: (Size) -> Unit = { },
listState: LazyListState = rememberLazyListState(),
rippleInteractionSource: List<MutableInteractionSource> = emptyList(),
rippleInteractionSource: Map<String, MutableInteractionSource> = emptyMap(),
recentItem: String? = null,
nextItem: String? = null,
onOpenItem: (String) -> Unit = { },
) {
val coroutineScope = rememberCoroutineScope()
@@ -125,9 +128,19 @@ fun MangaListingBottomSheet(
MangaListingBottomSheetLayout(
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("첫화보기") },
text = { Text(
when {
mangaListing.entries.any { it.itemID == recentItem } -> "이어보기"
mangaListing.entries.any { it.itemID == nextItem } -> "다음화보기"
else -> "첫화보기"
}
) },
onClick = {
mangaListing.entries.lastOrNull()?.let { onOpenItem(it.itemID) }
when {
mangaListing.entries.any { it.itemID == recentItem } -> onOpenItem(recentItem!!)
mangaListing.entries.any { it.itemID == nextItem } -> onOpenItem(nextItem!!)
else -> mangaListing.entries.lastOrNull()?.let { onOpenItem(it.itemID) }
}
}
)
},
@@ -216,11 +229,9 @@ fun MangaListingBottomSheet(
onOpenItem(entry.itemID)
}
.run {
rippleInteractionSource
.getOrNull(index)
?.let {
indication(it, rememberRipple())
} ?: this
rippleInteractionSource[entry.itemID]?.let {
indication(it, rememberRipple())
} ?: this
}
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,

View File

@@ -18,6 +18,7 @@
package xyz.quaver.pupil.sources.manatoki.composable
import android.util.Log
import androidx.activity.compose.BackHandler
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.animateFloatAsState
@@ -82,6 +83,7 @@ fun Reader(navController: NavController) {
val database: ManatokiDatabase by rememberInstance()
val favoriteDao = remember { database.favoriteDao() }
val bookmarkDao = remember { database.bookmarkDao() }
val historyDao = remember { database.historyDao() }
val coroutineScope = rememberCoroutineScope()
@@ -91,6 +93,9 @@ fun Reader(navController: NavController) {
LaunchedEffect(Unit) {
if (itemID != null)
client.getItem(itemID, onReader = {
coroutineScope.launch {
historyDao.insert(it.itemID, it.listingItemID, 1)
}
readerInfo = it
model.load(it.urls) {
set("User-Agent", imageUserAgent)
@@ -102,17 +107,24 @@ fun Reader(navController: NavController) {
val isFavorite by favoriteDao.contains(itemID ?: "").collectAsState(false)
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
var mangaListing: MangaListing? by rememberSaveable { mutableStateOf(null) }
val mangaListingRippleInteractionSource = remember { mutableStateListOf<MutableInteractionSource>() }
val navigationBarsPadding = LocalDensity.current.run {
rememberInsetsPaddingValues(
LocalWindowInsets.current.navigationBars
).calculateBottomPadding().toPx()
}
val bottomSheetListState = rememberLazyListState()
val readerListState = rememberLazyListState()
LaunchedEffect(readerListState.firstVisibleItemIndex) {
readerInfo?.let { readerInfo ->
historyDao.insert(
readerInfo.itemID,
readerInfo.listingItemID,
readerListState.firstVisibleItemIndex
)
}
}
var scrollDirection by remember { mutableStateOf(0f) }
BackHandler {
@@ -123,7 +135,10 @@ fun Reader(navController: NavController) {
}
}
var mangaListing: MangaListing? by rememberSaveable { mutableStateOf(null) }
val mangaListingListState = rememberLazyListState()
var mangaListingListSize: Size? by remember { mutableStateOf(null) }
val mangaListingRippleInteractionSource = remember { MutableInteractionSource() }
ModalBottomSheetLayout(
sheetState = sheetState,
@@ -132,11 +147,10 @@ fun Reader(navController: NavController) {
MangaListingBottomSheet(
mangaListing,
currentItemID = itemID,
onListSize = {
mangaListingListSize = it
},
rippleInteractionSource = mangaListingRippleInteractionSource,
listState = bottomSheetListState
onListSize = { mangaListingListSize = it },
rippleInteractionSource = if (itemID == null) emptyMap() else mapOf(itemID to mangaListingRippleInteractionSource),
listState = mangaListingListState,
nextItem = readerInfo?.nextItemID
) {
navController.navigate("manatoki.net/reader/$it") {
popUpTo("manatoki.net/")
@@ -214,7 +228,7 @@ fun Reader(navController: NavController) {
}
} else {
coroutineScope.launch {
sheetState.show()
sheetState.animateTo(ModalBottomSheetValue.Expanded)
}
coroutineScope.launch {
@@ -222,42 +236,31 @@ fun Reader(navController: NavController) {
client.getItem(it.listingItemID, onListing = {
mangaListing = it
mangaListingRippleInteractionSource.addAll(
List(
max(
it.entries.size - mangaListingRippleInteractionSource.size,
0
)
) {
MutableInteractionSource()
}
)
coroutineScope.launch {
while (bottomSheetListState.layoutInfo.totalItemsCount != it.entries.size) {
while (mangaListingListState.layoutInfo.totalItemsCount != it.entries.size) {
delay(100)
}
val targetIndex =
it.entries.indexOfFirst { it.itemID == itemID }
bottomSheetListState.scrollToItem(targetIndex)
mangaListingListState.scrollToItem(targetIndex)
mangaListingListSize?.let { sheetSize ->
val targetItem =
bottomSheetListState.layoutInfo.visibleItemsInfo.first {
mangaListingListState.layoutInfo.visibleItemsInfo.first {
it.key == itemID
}
if (targetItem.offset == 0) {
bottomSheetListState.animateScrollBy(
mangaListingListState.animateScrollBy(
-(sheetSize.height - navigationBarsPadding - targetItem.size)
)
}
delay(200)
with(mangaListingRippleInteractionSource[targetIndex]) {
with(mangaListingRippleInteractionSource) {
val interaction =
PressInteraction.Press(
Offset(

View File

@@ -24,16 +24,15 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.GridCells
import androidx.compose.foundation.lazy.LazyVerticalGrid
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.NavigateBefore
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.rememberInsetsPaddingValues
@@ -44,7 +43,6 @@ 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
import xyz.quaver.pupil.sources.manatoki.viewmodel.RecentViewModel
@@ -58,99 +56,74 @@ fun Recent(navController: NavController) {
val coroutineScope = rememberCoroutineScope()
var mangaListing: MangaListing? by rememberSaveable { mutableStateOf(null) }
val state = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
LaunchedEffect(Unit) {
model.load()
}
BackHandler {
if (state.isVisible) coroutineScope.launch { state.hide() }
else navController.popBackStack()
navController.popBackStack()
}
ModalBottomSheetLayout(
sheetState = state,
sheetShape = RoundedCornerShape(32.dp, 32.dp, 0.dp, 0.dp),
sheetContent = {
MangaListingBottomSheet(mangaListing) {
coroutineScope.launch {
client.getItem(it, onReader = {
launch {
state.snapTo(ModalBottomSheetValue.Hidden)
navController.navigate("manatoki.net/reader/${it.itemID}")
}
})
}
}
}
) {
Scaffold(
topBar = {
TopAppBar(
title = {
Text("최신 업데이트")
},
navigationIcon = {
IconButton(onClick = { navController.navigateUp() }) {
Icon(
Icons.Default.NavigateBefore,
contentDescription = null
)
}
},
contentPadding = rememberInsetsPaddingValues(
LocalWindowInsets.current.statusBars,
applyBottom = false
)
)
}
) { contentPadding ->
Box(Modifier.padding(contentPadding)) {
OverscrollPager(
currentPage = model.page,
prevPageAvailable = model.page > 1,
nextPageAvailable = model.page < 10,
nextPageTurnIndicatorOffset = rememberInsetsPaddingValues(
LocalWindowInsets.current.navigationBars
).calculateBottomPadding(),
onPageTurn = {
model.page = it
model.load()
Scaffold(
topBar = {
TopAppBar(
title = {
Text("최신 업데이트")
},
navigationIcon = {
IconButton(onClick = { navController.navigateUp() }) {
Icon(
Icons.Default.NavigateBefore,
contentDescription = null
)
}
) {
Box(Modifier.fillMaxSize()) {
LazyVerticalGrid(
GridCells.Adaptive(minSize = 200.dp),
contentPadding = rememberInsetsPaddingValues(
LocalWindowInsets.current.navigationBars
)
) {
items(model.result) {
Thumbnail(
it,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(3f / 4)
.padding(8.dp)
) {
coroutineScope.launch {
mangaListing = null
state.show()
}
coroutineScope.launch {
client.getItem(it, onListing = {
mangaListing = it
})
}
},
contentPadding = rememberInsetsPaddingValues(
LocalWindowInsets.current.statusBars,
applyBottom = false
)
)
}
) { contentPadding ->
Box(Modifier.padding(contentPadding)) {
OverscrollPager(
currentPage = model.page,
prevPageAvailable = model.page > 1,
nextPageAvailable = model.page < 10,
nextPageTurnIndicatorOffset = rememberInsetsPaddingValues(
LocalWindowInsets.current.navigationBars
).calculateBottomPadding(),
onPageTurn = {
model.page = it
model.load()
}
) {
Box(Modifier.fillMaxSize()) {
LazyVerticalGrid(
GridCells.Adaptive(minSize = 200.dp),
contentPadding = rememberInsetsPaddingValues(
LocalWindowInsets.current.navigationBars
)
) {
items(model.result) {
Thumbnail(
it,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(3f / 4)
.padding(8.dp)
) {
coroutineScope.launch {
client.getItem(it, onReader = {
navController.navigate("manatoki.net/reader/${it.itemID}")
})
}
}
}
if (model.loading)
CircularProgressIndicator(Modifier.align(Alignment.Center))
}
if (model.loading)
CircularProgressIndicator(Modifier.align(Alignment.Center))
}
}
}

View File

@@ -22,12 +22,12 @@ import android.util.Log
import androidx.activity.compose.BackHandler
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.GridCells
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyVerticalGrid
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
@@ -42,8 +42,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
@@ -56,15 +59,14 @@ 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.sources.composable.ModalTopSheetLayout
import xyz.quaver.pupil.sources.composable.ModalTopSheetState
import xyz.quaver.pupil.sources.composable.OverscrollPager
import xyz.quaver.pupil.sources.manatoki.Chip
import xyz.quaver.pupil.sources.manatoki.MangaListing
import xyz.quaver.pupil.sources.manatoki.getItem
import xyz.quaver.pupil.sources.manatoki.*
import xyz.quaver.pupil.sources.manatoki.viewmodel.*
@ExperimentalFoundationApi
@@ -75,14 +77,15 @@ fun Search(navController: NavController) {
val client: HttpClient by rememberInstance()
val database: ManatokiDatabase by rememberInstance()
val historyDao = remember { database.historyDao() }
var searchFocused by remember { mutableStateOf(false) }
val handleOffset by animateDpAsState(if (searchFocused) 0.dp else (-36).dp)
val drawerState = rememberSwipeableState(ModalTopSheetState.Hidden)
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
var mangaListing: MangaListing? by rememberSaveable { mutableStateOf(null) }
val coroutineScope = rememberCoroutineScope()
val focusManager = LocalFocusManager.current
@@ -100,11 +103,28 @@ fun Search(navController: NavController) {
}
}
var mangaListing: MangaListing? by rememberSaveable { mutableStateOf(null) }
var recentItem: String? by rememberSaveable { mutableStateOf(null) }
val mangaListingListState = rememberLazyListState()
var mangaListingListSize: Size? by remember { mutableStateOf(null) }
val mangaListingInteractionSource = remember { mutableStateMapOf<String, MutableInteractionSource>() }
val navigationBarsPadding = LocalDensity.current.run {
rememberInsetsPaddingValues(
LocalWindowInsets.current.navigationBars
).calculateBottomPadding().toPx()
}
ModalBottomSheetLayout(
sheetState = sheetState,
sheetShape = RoundedCornerShape(32.dp, 32.dp, 0.dp, 0.dp),
sheetContent = {
MangaListingBottomSheet(mangaListing) {
MangaListingBottomSheet(
mangaListing,
onListSize = { mangaListingListSize = it },
rippleInteractionSource = mangaListingInteractionSource,
listState = mangaListingListState,
recentItem = recentItem
) {
coroutineScope.launch {
client.getItem(it, onReader = {
launch {
@@ -201,17 +221,6 @@ fun Search(navController: NavController) {
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
var expanded by remember { mutableStateOf(false) }
val suggestedArtists = remember(model.artist) {
if (model.artist.isEmpty())
model.availableArtists
else
model
.availableArtists
.filter { it.contains(model.artist) }
.sortedBy { if (it.startsWith(model.artist)) 0 else 1 }
}.take(20)
Text("작가")
TextField(model.artist, onValueChange = { model.artist = it })
@@ -266,7 +275,10 @@ fun Search(navController: NavController) {
}
}
Box(Modifier.fillMaxWidth().height(8.dp))
Box(
Modifier
.fillMaxWidth()
.height(8.dp))
}
}
) {
@@ -301,11 +313,58 @@ fun Search(navController: NavController) {
) {
coroutineScope.launch {
mangaListing = null
sheetState.show()
sheetState.animateTo(ModalBottomSheetValue.Expanded)
}
coroutineScope.launch {
client.getItem(it, onListing = {
mangaListing = it
coroutineScope.launch {
val recentItemID = historyDao.getAll(it.itemID).firstOrNull() ?: return@launch
recentItem = recentItemID
while (mangaListingListState.layoutInfo.totalItemsCount != it.entries.size) {
delay(100)
}
val interactionSource = mangaListingInteractionSource.getOrPut(recentItemID) {
MutableInteractionSource()
}
val targetIndex =
it.entries.indexOfFirst { entry -> entry.itemID == recentItemID }
mangaListingListState.scrollToItem(targetIndex)
mangaListingListSize?.let { sheetSize ->
val targetItem =
mangaListingListState.layoutInfo.visibleItemsInfo.first {
it.key == recentItemID
}
if (targetItem.offset == 0) {
mangaListingListState.animateScrollBy(
-(sheetSize.height - navigationBarsPadding - targetItem.size)
)
}
delay(200)
with(interactionSource) {
val interaction =
PressInteraction.Press(
Offset(
sheetSize.width / 2,
targetItem.size / 2f
)
)
emit(interaction)
emit(PressInteraction.Release(interaction))
}
}
}
})
}
}

View File

@@ -103,7 +103,7 @@ fun Chip(text: String, selected: Boolean = false, onClick: () -> Unit = { }) {
}
}
private val cache = LruCache<String, Any>(50)
private val cache = LruCache<String, Any>(100)
suspend fun HttpClient.getItem(
itemID: String,
onListing: (MangaListing) -> Unit = { },
@@ -144,7 +144,7 @@ suspend fun HttpClient.getItem(
}.toString()
val urls = Jsoup.parse(htmlData)
.select("img[^data-]:not([style]):not([^class])")
.select("img[^data-]:not([style])")
.map {
it.attributes()
.first { it.key.startsWith("data-") }

View File

@@ -19,6 +19,7 @@
package xyz.quaver.pupil.sources.manatoki.viewmodel
import android.app.Application
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateListOf
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
@@ -36,6 +37,7 @@ 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.sources.manatoki.HistoryDao
import xyz.quaver.pupil.sources.manatoki.composable.Thumbnail
import xyz.quaver.pupil.sources.manatoki.manatokiUrl
import xyz.quaver.pupil.sources.manatoki.waitForRateLimit

View File

@@ -17,7 +17,7 @@
*/
const val GROUP_ID = "xyz.quaver"
const val VERSION = "6.0.0-alpha01"
const val VERSION = "6.0.0-alpha02"
object Versions {
const val KOTLIN_VERSION = "1.5.31"