diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 8ec0174f..8a4d8845 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -1,17 +1,17 @@ - + - + - - + + - - + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 69c047e2..d36af219 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -110,10 +110,10 @@ dependencies { implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.0") implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0") - implementation("androidx.room:room-runtime:2.3.0") - annotationProcessor("androidx.room:room-compiler:2.3.0") - kapt("androidx.room:room-compiler:2.3.0") - implementation("androidx.room:room-ktx:2.3.0") + implementation("androidx.room:room-runtime:2.4.0") + annotationProcessor("androidx.room:room-compiler:2.4.0") + kapt("androidx.room:room-compiler:2.4.0") + implementation("androidx.room:room-ktx:2.4.0") implementation("androidx.datastore:datastore:1.0.0") implementation("androidx.datastore:datastore-preferences:1.0.0") @@ -138,6 +138,8 @@ dependencies { implementation("xyz.quaver:documentfilex:0.7.1") implementation("xyz.quaver:subsampledimage:0.0.1-alpha11-SNAPSHOT") + implementation("com.google.guava:guava:31.0.1-android") + implementation("org.kodein.log:kodein-log:0.11.1") debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7") diff --git a/app/src/main/java/xyz/quaver/pupil/sources/Common.kt b/app/src/main/java/xyz/quaver/pupil/sources/Common.kt index a747af3f..33975834 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/Common.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/Common.kt @@ -23,6 +23,7 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import org.kodein.di.* import xyz.quaver.pupil.sources.hitomi.Hitomi +import xyz.quaver.pupil.sources.manatoki.Manatoki abstract class Source { abstract val name: String @@ -39,7 +40,7 @@ val sourceModule = DI.Module(name = "source") { listOf<(Application) -> (Source)>( { Hitomi(it) }, //{ Hiyobi_io(it) }, - //{ Manatoki(it) } + { Manatoki(it) } ).forEach { source -> inSet { singleton { source(instance()).let { it.name to it } } } } diff --git a/app/src/main/java/xyz/quaver/pupil/sources/composable/ReaderBase.kt b/app/src/main/java/xyz/quaver/pupil/sources/composable/ReaderBase.kt index 3bac274e..7e05f5aa 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/composable/ReaderBase.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/composable/ReaderBase.kt @@ -41,13 +41,16 @@ import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.google.accompanist.insets.LocalWindowInsets +import com.google.accompanist.insets.navigationBarsPadding import com.google.accompanist.insets.rememberInsetsPaddingValues import com.google.accompanist.insets.ui.Scaffold import com.google.accompanist.insets.ui.TopAppBar -import com.google.accompanist.systemuicontroller.rememberSystemUiController import io.ktor.client.request.* import io.ktor.http.* import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -63,6 +66,7 @@ import xyz.quaver.pupil.R import xyz.quaver.pupil.db.AppDatabase import xyz.quaver.pupil.ui.theme.Orange500 import xyz.quaver.pupil.util.NetworkCache +import xyz.quaver.pupil.util.activity import xyz.quaver.pupil.util.rememberFileXImageSource import kotlin.math.abs @@ -75,9 +79,6 @@ open class ReaderBaseViewModel(app: Application) : AndroidViewModel(app), DIAwar private val database: AppDatabase by instance() - private val historyDao = database.historyDao() - private val bookmarkDao = database.bookmarkDao() - var error by mutableStateOf(false) var title by mutableStateOf(null) @@ -171,6 +172,19 @@ fun ReaderBase( val scaffoldState = rememberScaffoldState() val snackbarCoroutineScope = rememberCoroutineScope() + LaunchedEffect(model.isFullscreen) { + context.activity?.window?.let { window -> + ViewCompat.getWindowInsetsController(window.decorView)?.let { + if (model.isFullscreen) { + it.systemBarsBehavior = + WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + it.hide(WindowInsetsCompat.Type.systemBars()) + } else + it.show(WindowInsetsCompat.Type.systemBars()) + } + } + } + if (model.error) stringResource(R.string.reader_failed_to_find_gallery).let { snackbarCoroutineScope.launch { @@ -181,16 +195,6 @@ fun ReaderBase( } } - val systemUiController = rememberSystemUiController() - val useDarkIcons = MaterialTheme.colors.isLight - - SideEffect { - systemUiController.setSystemBarsColor( - color = Color.Transparent, - darkIcons = useDarkIcons - ) - } - Scaffold( topBar = { if (!model.isFullscreen) @@ -225,6 +229,7 @@ fun ReaderBase( floatingActionButton = { if (!model.isFullscreen) MultipleFloatingActionButton( + modifier = Modifier.navigationBarsPadding(), items = listOf( SubFabItem( icon = Icons.Default.Fullscreen, @@ -245,7 +250,8 @@ fun ReaderBase( Box(Modifier.padding(contentPadding)) { LazyColumn( Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(4.dp) + verticalArrangement = Arrangement.spacedBy(4.dp), + contentPadding = rememberInsetsPaddingValues(LocalWindowInsets.current.navigationBars) ) { itemsIndexed(model.imageList) { i, uri -> val state = rememberSubSampledImageState(ScaleTypes.FIT_WIDTH) diff --git a/app/src/main/java/xyz/quaver/pupil/sources/composable/SearchBase.kt b/app/src/main/java/xyz/quaver/pupil/sources/composable/SearchBase.kt index cde02388..111a2c8e 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/composable/SearchBase.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/composable/SearchBase.kt @@ -35,7 +35,6 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource @@ -56,9 +55,8 @@ import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.google.accompanist.insets.LocalWindowInsets import com.google.accompanist.insets.navigationBarsPadding import com.google.accompanist.insets.rememberInsetsPaddingValues -import com.google.accompanist.insets.systemBarsPadding +import com.google.accompanist.insets.statusBarsPadding import com.google.accompanist.insets.ui.Scaffold -import com.google.accompanist.systemuicontroller.rememberSystemUiController import xyz.quaver.pupil.R import xyz.quaver.pupil.ui.theme.LightBlue300 import kotlin.math.* @@ -119,25 +117,15 @@ fun SearchBase( } } - val systemBarsPaddingValues = rememberInsetsPaddingValues(insets = LocalWindowInsets.current.systemBars) + val statusBarsPaddingValues = rememberInsetsPaddingValues(insets = LocalWindowInsets.current.statusBars) val pageTurnIndicatorHeight = LocalDensity.current.run { 64.dp.toPx() } - val searchBarDefaultOffset = systemBarsPaddingValues.calculateTopPadding() + 64.dp + val searchBarDefaultOffset = statusBarsPaddingValues.calculateTopPadding() + 64.dp val searchBarDefaultOffsetPx = LocalDensity.current.run { searchBarDefaultOffset.roundToPx() } var overscroll: Float? by remember { mutableStateOf(null) } - val systemUiController = rememberSystemUiController() - val useDarkIcons = MaterialTheme.colors.isLight - - SideEffect { - systemUiController.setSystemBarsColor( - color = Color.Transparent, - darkIcons = useDarkIcons - ) - } - LaunchedEffect(navigationIconProgress) { navigationIcon.progress = navigationIconProgress } @@ -307,7 +295,7 @@ fun SearchBase( FloatingSearchBar( modifier = Modifier - .systemBarsPadding() + .statusBarsPadding() .offset(0.dp, LocalDensity.current.run { model.searchBarOffset.toDp() }), query = model.query, onQueryChange = { model.query = it }, diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/SourceSelectDialog.kt b/app/src/main/java/xyz/quaver/pupil/sources/composable/SourceSelectDialog.kt similarity index 82% rename from app/src/main/java/xyz/quaver/pupil/ui/dialog/SourceSelectDialog.kt rename to app/src/main/java/xyz/quaver/pupil/sources/composable/SourceSelectDialog.kt index 4e68bf32..c1360d33 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/dialog/SourceSelectDialog.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/composable/SourceSelectDialog.kt @@ -1,6 +1,6 @@ /* * Pupil, Hitomi.la viewer for Android - * Copyright (C) 2020 tom5079 + * 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 @@ -13,13 +13,12 @@ * 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 . + * along with this program. If not, see . */ -package xyz.quaver.pupil.ui.dialog +package xyz.quaver.pupil.sources.composable import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* @@ -29,13 +28,23 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog +import androidx.navigation.NavController import org.kodein.di.compose.rememberInstance import xyz.quaver.pupil.sources.Source import xyz.quaver.pupil.sources.SourceEntries +@Composable +fun SourceSelectDialog(navController: NavController, currentSource: String, onDismissRequest: () -> Unit = { }) { + SourceSelectDialog(currentSource = currentSource, onDismissRequest = onDismissRequest) { + onDismissRequest() + navController.navigate(it.name) { + popUpTo(currentSource) { inclusive = true } + } + } +} + @Composable fun SourceSelectDialogItem(source: Source, isSelected: Boolean, onSelected: (Source) -> Unit = { }) { Row( @@ -86,4 +95,4 @@ fun SourceSelectDialog(currentSource: String, onDismissRequest: () -> Unit = { } } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt index e09bafa9..3a24a881 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt @@ -41,7 +41,6 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import androidx.navigation.compose.navigation import io.ktor.client.* -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.kodein.di.DIAware import org.kodein.di.android.closestDI @@ -57,7 +56,6 @@ import xyz.quaver.pupil.sources.hitomi.composable.DetailedSearchResult 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.dialog.SourceSelectDialog class Hitomi(app: Application) : Source(), DIAware { override val di by closestDI(app) @@ -73,9 +71,9 @@ class Hitomi(app: Application) : Source(), DIAware { override val iconResID: Int = R.drawable.hitomi override fun NavGraphBuilder.navGraph(navController: NavController) { - navigation(startDestination = "search", route = name) { - composable("search") { Search(navController) } - composable("reader/{itemID}") { Reader(navController) } + navigation(startDestination = "hitomi.la/search", route = name) { + composable("hitomi.la/search") { Search(navController) } + composable("hitomi.la/reader/{itemID}") { Reader(navController) } } } @@ -94,16 +92,7 @@ class Hitomi(app: Application) : Source(), DIAware { var sourceSelectDialog by remember { mutableStateOf(false) } if (sourceSelectDialog) - SourceSelectDialog( - currentSource = name, - onDismissRequest = { sourceSelectDialog = false } - ) { - sourceSelectDialog = false - navController.navigate("main/${it.name}") { - launchSingleTop = true - popUpTo("main/{source}") { inclusive = true } - } - } + SourceSelectDialog(navController, name) { sourceSelectDialog = false } LaunchedEffect(model.currentPage, model.sortByPopularity) { model.search() @@ -188,7 +177,10 @@ class Hitomi(app: Application) : Source(), DIAware { } } ) { result -> - navController.navigate("reader/${result.itemID}") + logger.info { + result.toString() + } + navController.navigate("hitomi.la/reader/${result.itemID}") } } } @@ -209,21 +201,19 @@ class Hitomi(app: Application) : Source(), DIAware { val bookmark by bookmarkDao.contains(name, itemID).observeAsState(false) - LaunchedEffect(model) { - launch(Dispatchers.IO) { - kotlin.runCatching { - val galleryID = itemID.toInt() + LaunchedEffect(itemID) { + runCatching { + val galleryID = itemID.toInt() - val galleryInfo = getGalleryInfo(client, galleryID) + val galleryInfo = getGalleryInfo(client, galleryID) - model.title = galleryInfo.title + model.title = galleryInfo.title - model.load(galleryInfo.files.map { imageUrlFromImage(galleryID, it, false) }) { - append("Referer", getReferer(galleryID)) - } - }.onFailure { - model.error = true + model.load(galleryInfo.files.map { imageUrlFromImage(galleryID, it, false) }) { + append("Referer", getReferer(galleryID)) } + }.onFailure { + model.error = true } } diff --git a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/Manatoki.kt b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/Manatoki.kt index 1d9c8eac..a3decdf4 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/Manatoki.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/Manatoki.kt @@ -1,101 +1,339 @@ -///* -// * 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 . -// */ -// -//package xyz.quaver.pupil.sources.manatoki -// -//import android.app.Application -//import kotlinx.coroutines.Dispatchers -//import kotlinx.coroutines.channels.Channel -//import kotlinx.coroutines.coroutineScope -//import kotlinx.coroutines.withContext -//import kotlinx.parcelize.Parcelize -//import org.jsoup.Jsoup -//import org.kodein.di.DIAware -//import org.kodein.di.android.closestDI -//import org.kodein.log.LoggerFactory -//import org.kodein.log.newLogger -//import xyz.quaver.pupil.R -//import xyz.quaver.pupil.sources.ItemInfo -//import xyz.quaver.pupil.sources.Source -// -//@Parcelize -//class ManatokiItemInfo( -// override val itemID: String, -// override val title: String -//) : ItemInfo { -// override val source: String = "manatoki.net" -//} -// -//class Manatoki(app: Application) : Source(), DIAware { -// override val di by closestDI(app) -// -// private val logger = newLogger(LoggerFactory.default) -// -// override val name = "manatoki.net" -// override val availableSortMode: List = emptyList() -// override val iconResID: Int = R.drawable.manatoki -// -// override suspend fun search( -// query: String, -// range: IntRange, -// sortMode: Int -// ): Pair, Int> { -// TODO("Not yet implemented") -// } -// -// override suspend fun images(itemID: String): List = coroutineScope { -// val jsoup = withContext(Dispatchers.IO) { -// Jsoup.connect("https://manatoki116.net/comic/$itemID").get() -// } -// -// val htmlData = jsoup -// .selectFirst(".view-padding > script")!! -// .data() -// .splitToSequence('\n') -// .fold(StringBuilder()) { sb, line -> -// if (!line.startsWith("html_data")) return@fold sb -// -// line.drop(12).dropLast(2).split('.').forEach { -// if (it.isNotBlank()) sb.appendCodePoint(it.toInt(16)) -// } -// sb -// }.toString() -// -// Jsoup.parse(htmlData) -// .select("img[^data-]:not([style])") -// .map { -// it.attributes() -// .first { it.key.startsWith("data-") } -// .value -// } -// } -// -// override suspend fun info(itemID: String): ItemInfo = coroutineScope { -// val jsoup = withContext(Dispatchers.IO) { -// Jsoup.connect("https://manatoki116.net/comic/$itemID").get() -// } -// -// val title = jsoup.selectFirst(".toon-title")!!.ownText() -// -// ManatokiItemInfo( -// itemID, -// title -// ) -// } -// -//} \ No newline at end of file +/* + * 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 . + */ +package xyz.quaver.pupil.sources.manatoki + +import android.app.Application +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Settings +import androidx.compose.runtime.* +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +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 androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import androidx.navigation.navigation +import com.google.accompanist.insets.LocalWindowInsets +import com.google.accompanist.insets.navigationBarsPadding +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.channels.Channel +import kotlinx.coroutines.launch +import org.kodein.di.DIAware +import org.kodein.di.android.closestDI +import org.kodein.di.compose.rememberInstance +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.sources.Source +import xyz.quaver.pupil.sources.composable.ReaderBase +import xyz.quaver.pupil.sources.composable.ReaderBaseViewModel +import xyz.quaver.pupil.sources.composable.SourceSelectDialog +import xyz.quaver.pupil.sources.manatoki.composable.BoardButton +import xyz.quaver.pupil.sources.manatoki.composable.MangaListingBottomSheet +import xyz.quaver.pupil.sources.manatoki.composable.Thumbnail +import xyz.quaver.pupil.sources.manatoki.viewmodel.MainViewModel +import java.util.concurrent.ConcurrentHashMap + +class Manatoki(app: Application) : Source(), DIAware { + override val di by closestDI(app) + + private val logger = newLogger(LoggerFactory.default) + + override val name = "manatoki.net" + override val iconResID = R.drawable.manatoki + + private val readerInfoChannel = ConcurrentHashMap>() + + override fun NavGraphBuilder.navGraph(navController: NavController) { + navigation(route = name, startDestination = "manatoki.net/") { + composable("manatoki.net/") { Main(navController) } + composable("manatoki.net/reader/{itemID}") { Reader(navController) } + } + } + + @OptIn(ExperimentalMaterialApi::class) + @Composable + fun Main(navController: NavController) { + val model: MainViewModel = viewModel() + + val client: HttpClient by rememberInstance() + + val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden) + var mangaListing: MangaListing? by rememberSaveable { mutableStateOf(null) } + + val coroutineScope = rememberCoroutineScope() + + val onListing: (MangaListing) -> Unit = { + mangaListing = it + logger.info { + it.toString() + } + coroutineScope.launch { + sheetState.show() + } + } + + val onReader: (ReaderInfo) -> Unit = { readerInfo -> + val channel = Channel() + readerInfoChannel[readerInfo.itemID] = channel + + coroutineScope.launch { + channel.send(readerInfo) + } + navController.navigate("manatoki.net/reader/${readerInfo.itemID}") + } + + var sourceSelectDialog by remember { mutableStateOf(false) } + + if (sourceSelectDialog) + SourceSelectDialog(navController, name) { sourceSelectDialog = false } + + LaunchedEffect(Unit) { + navController.backQueue.forEach { + logger.info { + it.destination.route.toString() + } + } + model.load() + } + + BackHandler { + if (sheetState.currentValue == ModalBottomSheetValue.Hidden) + navController.popBackStack() + else + coroutineScope.launch { + sheetState.hide() + } + } + + ModalBottomSheetLayout( + sheetState = sheetState, + sheetShape = RoundedCornerShape(32.dp, 32.dp, 0.dp, 0.dp), + sheetContent = { + MangaListingBottomSheet(mangaListing) { + coroutineScope.launch { + client.getItem(it, onListing, onReader) + } + } + } + ) { + Scaffold( + topBar = { + TopAppBar( + title = { + Text("박사장 게섯거라") + }, + actions = { + IconButton(onClick = { sourceSelectDialog = true }) { + Image( + painter = painterResource(id = R.drawable.manatoki), + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + } + + IconButton(onClick = { navController.navigate("settings") }) { + Icon(Icons.Default.Settings, contentDescription = null) + } + }, + contentPadding = rememberInsetsPaddingValues( + insets = LocalWindowInsets.current.statusBars, + applyBottom = false + ) + ) + } + ) { contentPadding -> + Box(Modifier.padding(contentPadding)) { + Column( + Modifier + .padding(8.dp, 0.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + "최신화", + style = MaterialTheme.typography.h5 + ) + + LazyRow( + modifier = Modifier + .fillMaxWidth() + .height(210.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + items(model.recentUpload) { item -> + Thumbnail(item) { + coroutineScope.launch { + client.getItem(it, onListing, onReader) + } + } + } + } + + Divider() + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + BoardButton("마나게시판", Color(0xFF007DB4)) + BoardButton("유머/가십", Color(0xFFF09614)) + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + BoardButton("역식자게시판", Color(0xFFA0C850)) + BoardButton("원본게시판", Color(0xFFFF4500)) + } + } + + Text("만화 목록", style = MaterialTheme.typography.h5) + LazyRow( + modifier = Modifier + .fillMaxWidth() + .height(210.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + items(model.mangaList) { item -> + Thumbnail(item) { + coroutineScope.launch { + client.getItem(it, onListing, onReader) + } + } + } + } + + Text("주간 베스트", style = MaterialTheme.typography.h5) + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + model.topWeekly.forEachIndexed { index, item -> + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + (index + 1).toString(), + modifier = Modifier + .background(Color(0xFF64C3F5)) + .width(24.dp), + color = Color.White, + textAlign = TextAlign.Center + ) + + Text( + item.title, + modifier = Modifier.weight(1f), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + + Text( + item.count, + color = Color(0xFFFF4500) + ) + } + } + } + + Box(Modifier.navigationBarsPadding()) + } + } + } + } + } + + @Composable + fun Reader(navController: NavController) { + val model: ReaderBaseViewModel = viewModel() + + val database: AppDatabase by rememberInstance() + val bookmarkDao = database.bookmarkDao() + + val coroutineScope = rememberCoroutineScope() + + val itemID = navController.currentBackStackEntry?.arguments?.getString("itemID") + + LaunchedEffect(Unit) { + val channel = itemID?.let { readerInfoChannel.remove(it) } + + if (channel == null) + model.error = true + else { + val readerInfo = channel.receive() + + model.title = readerInfo.title + model.load(readerInfo.urls) + } + } + + val bookmark by bookmarkDao.contains(name, itemID ?: "").observeAsState(false) + + BackHandler { + if (model.isFullscreen) + model.isFullscreen = false + else + navController.popBackStack() + } + + ReaderBase( + model, + icon = { + Image( + painter = painterResource(R.drawable.manatoki), + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + }, + bookmark = bookmark, + onToggleBookmark = { + if (itemID != null) + coroutineScope.launch { + if (bookmark) bookmarkDao.delete(name, itemID) + else bookmarkDao.insert(name, itemID) + } + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/BoardButton.kt b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/BoardButton.kt new file mode 100644 index 00000000..628fa27e --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/BoardButton.kt @@ -0,0 +1,66 @@ +/* + * 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 . + */ + +package xyz.quaver.pupil.sources.manatoki.composable + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowForward +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun RowScope.BoardButton( + text: String, + color: Color +) { + Card( + modifier = Modifier.height(64.dp).weight(1f), + shape = RoundedCornerShape(12.dp), + elevation = 8.dp + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text, + modifier = Modifier.padding(8.dp, 0.dp).weight(1f), + style = MaterialTheme.typography.h6 + ) + + Icon( + Icons.Default.ArrowForward, + contentDescription = null, + tint = Color.White, + modifier = Modifier + .width(48.dp) + .fillMaxHeight() + .background(color) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/MangaListingBottomSheet.kt b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/MangaListingBottomSheet.kt new file mode 100644 index 00000000..4bc30ec9 --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/MangaListingBottomSheet.kt @@ -0,0 +1,209 @@ +/* + * 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 . + */ + +package xyz.quaver.pupil.sources.manatoki.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.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.SubcomposeLayout +import androidx.compose.ui.unit.dp +import coil.compose.rememberImagePainter +import com.google.accompanist.flowlayout.FlowRow +import com.google.accompanist.insets.LocalWindowInsets +import com.google.accompanist.insets.navigationBarsPadding +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 enum class MangaListingBottomSheetLayoutContent { Top, Bottom, Fab } + +@Composable +fun MangaListingBottomSheetLayout( + modifier: Modifier = Modifier, + floatingActionButton: @Composable () -> Unit, + top: @Composable () -> Unit, + bottom: @Composable () -> Unit +) { + SubcomposeLayout { constraints -> + val layoutWidth = constraints.maxWidth + val layoutHeight = constraints.maxHeight * HeightPercentage / 100 + + layout(layoutWidth, layoutHeight) { + val topPlaceables = subcompose(MangaListingBottomSheetLayoutContent.Top, top).map { + it.measure(constraints) + } + + val topPlaceableHeight = topPlaceables.maxOfOrNull { it.height } ?: 0 + + val bottomConstraints = constraints.copy( + maxHeight = layoutHeight - topPlaceableHeight + ) + + val bottomPlaceables = subcompose(MangaListingBottomSheetLayoutContent.Bottom, bottom).map { + it.measure(bottomConstraints) + } + + val fabPlaceables = subcompose(MangaListingBottomSheetLayoutContent.Fab, floatingActionButton).mapNotNull { + it.measure(constraints).takeIf { it.height != 0 && it.width != 0 } + } + + topPlaceables.forEach { it.place(0, 0) } + bottomPlaceables.forEach { it.place(0, topPlaceableHeight) } + + if (fabPlaceables.isNotEmpty()) { + val fabWidth = fabPlaceables.maxOf { it.width } + val fabHeight = fabPlaceables.maxOf { it.height } + + fabPlaceables.forEach { + it.place( + layoutWidth - fabWidth - FabSpacing.roundToPx(), + topPlaceableHeight - fabHeight / 2 + ) + } + } + } + } +} + +@Composable +fun MangaListingBottomSheet( + mangaListing: MangaListing? = null, + onOpenItem: (String) -> Unit = { } +) { + Box( + modifier = Modifier.fillMaxWidth() + ) { + mangaListing?.run { + MangaListingBottomSheetLayout( + floatingActionButton = { + ExtendedFloatingActionButton( + text = { Text("첫화보기") }, + onClick = { entries.lastOrNull()?.let { onOpenItem(it.itemID) } } + ) + }, + top = { + Row( + modifier = Modifier + .height(IntrinsicSize.Min) + .background(MaterialTheme.colors.primary) + .padding(0.dp, 0.dp, 0.dp, 4.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + val painter = rememberImagePainter(thumbnail) + + Image( + modifier = Modifier + .width(150.dp) + .aspectRatio( + with(painter.intrinsicSize) { if (this == androidx.compose.ui.geometry.Size.Unspecified) 1f else width / height }, + true + ), + painter = painter, + contentDescription = null + ) + + Column( + modifier = Modifier + .weight(1f) + .padding(0.dp, 8.dp) + .fillMaxHeight(), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + title, + style = MaterialTheme.typography.h5, + modifier = Modifier.weight(1f) + ) + + CompositionLocalProvider(LocalContentAlpha provides 0.7f) { + Text("작가: $author") + + Row(verticalAlignment = Alignment.CenterVertically) { + Text("분류: ") + + CompositionLocalProvider(LocalContentAlpha provides 1f) { + FlowRow( + modifier = Modifier.weight(1f), + mainAxisSpacing = 8.dp + ) { + tags.forEach { + Card( + elevation = 4.dp + ) { + Text( + it, + style = MaterialTheme.typography.caption, + modifier = Modifier.padding(4.dp) + ) + } + } + } + } + } + + Text("발행구분: $type") + } + } + } + }, + bottom = { + LazyColumn( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = rememberInsetsPaddingValues(LocalWindowInsets.current.navigationBars) + ) { + items(entries) { entry -> + Row( + modifier = Modifier + .clickable { + onOpenItem(entry.itemID) + } + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + entry.title, + style = MaterialTheme.typography.h6, + modifier = Modifier.weight(1f) + ) + + Text("★ ${entry.starRating}") + } + Divider() + } + } + } + ) + } ?: run { + CircularProgressIndicator( + Modifier.align(Alignment.Center).navigationBarsPadding().padding(16.dp) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Thumbnail.kt b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Thumbnail.kt new file mode 100644 index 00000000..e8dfdc52 --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Thumbnail.kt @@ -0,0 +1,79 @@ +/* + * 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 . + */ + +package xyz.quaver.pupil.sources.manatoki.composable + +import android.os.Parcelable +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.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import coil.compose.rememberImagePainter +import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable + +@Parcelize +@Serializable +data class Thumbnail( + val itemID: String, + val title: String, + val thumbnail: String +): Parcelable + +@Composable +fun Thumbnail( + thumbnail: Thumbnail, + onClick: (String) -> Unit = { } +) { + Card( + shape = RoundedCornerShape(12.dp), + elevation = 8.dp, + modifier = Modifier.clickable { onClick(thumbnail.itemID) } + ) { + Box( + modifier = Modifier.width(IntrinsicSize.Min) + ) { + Image( + modifier = Modifier.size(180.dp, 210.dp), + painter = rememberImagePainter(thumbnail.thumbnail), + contentDescription = null + ) + + Text( + thumbnail.title, + color = Color.White, + modifier = Modifier + .align(Alignment.BottomStart) + .fillMaxWidth() + .background(Color.Black.copy(alpha = 0.7f)) + .padding(8.dp), + softWrap = true, + style = MaterialTheme.typography.subtitle1 + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/util.kt b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/util.kt new file mode 100644 index 00000000..9c20f9b4 --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/util.kt @@ -0,0 +1,183 @@ +/* + * 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 . + */ + +package xyz.quaver.pupil.sources.manatoki + +import android.os.Parcelable +import com.google.common.util.concurrent.RateLimiter +import io.ktor.client.* +import io.ktor.client.request.* +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.withContext +import kotlinx.coroutines.yield +import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable +import org.jsoup.Jsoup +import java.util.concurrent.Executors + +private val rateLimitCoroutineDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() +private val rateLimiter = RateLimiter.create(10.0) + +suspend fun waitForRateLimit() { + withContext(rateLimitCoroutineDispatcher) { + rateLimiter.acquire() + } + yield() +} + +@Parcelize +@Serializable +data class MangaListingEntry( + val itemID: String, + val episode: Int, + val title: String, + val starRating: Float, + val date: String, + val viewCount: Int, + val thumbsUpCount: Int +): Parcelable + +@Parcelize +@Serializable +data class MangaListing( + val itemID: String, + val title: String, + val thumbnail: String, + val author: String, + val tags: List, + val type: String, + val thumbsUpCount: Int, + val entries: List +): Parcelable + +@Parcelize +@Serializable +data class ReaderInfo( + val itemID: String, + val title: String, + val urls: List +): Parcelable + +suspend fun HttpClient.getItem( + itemID: String, + onListing: (MangaListing) -> Unit, + onReader: (ReaderInfo) -> Unit +) = coroutineScope { + waitForRateLimit() + val content: String = get("https://manatoki116.net/comic/$itemID") + + val doc = Jsoup.parse(content) + + yield() + + if (doc.getElementsByClass("serial-list").size == 0) { + val htmlData = doc + .selectFirst(".view-padding > script")!! + .data() + .splitToSequence('\n') + .fold(StringBuilder()) { sb, line -> + if (!line.startsWith("html_data")) return@fold sb + + line.drop(12).dropLast(2).split('.').forEach { + if (it.isNotBlank()) sb.appendCodePoint(it.toInt(16)) + } + sb + }.toString() + + val urls = Jsoup.parse(htmlData) + .select("img[^data-]:not([style])") + .map { + it.attributes() + .first { it.key.startsWith("data-") } + .value + } + + val title = doc.getElementsByClass("toon-title").first()!!.ownText() + + onReader(ReaderInfo(itemID, title, urls)) + } else { + val titleBlock = doc.selectFirst("div.view-title")!! + + val title = titleBlock.select("div.view-content:not([itemprop])").first()!!.text() + + val author = + titleBlock + .select("div.view-content:not([itemprop]):contains(작가)") + .first()!! + .getElementsByTag("a") + .first()!! + .text() + + val tags = + titleBlock + .select("div.view-content:not([itemprop]):contains(분류)") + .first()!! + .getElementsByTag("a") + .map { it.text() } + + val type = + titleBlock + .select("div.view-content:not([itemprop]):contains(발행구분)") + .first()!! + .getElementsByTag("a") + .first()!! + .text() + + val thumbnail = + titleBlock.getElementsByTag("img").first()!!.attr("src") + + val thumbsUpCount = + titleBlock.select("i.fa-thumbs-up + b").text().toInt() + + val entries = + doc.select("div.serial-list .list-item").map { + val episode = it.getElementsByClass("wr-num").first()!!.text().toInt() + val (itemID, title) = it.getElementsByClass("item-subject").first()!!.let { subject -> + subject.attr("href").dropLastWhile { it != '?' }.dropLast(1).takeLastWhile { it != '/' } to subject.ownText() + } + val starRating = it.getElementsByClass("wr-star").first()!!.text().drop(1).takeWhile { it != ')' }.toFloat() + val date = it.getElementsByClass("wr-date").first()!!.text() + val viewCount = it.getElementsByClass("wr-hit").first()!!.text().replace(",", "").toInt() + val thumbsUpCount = it.getElementsByClass("wr-good").first()!!.text().replace(",", "").toInt() + + MangaListingEntry( + itemID, + episode, + title, + starRating, + date, + viewCount, + thumbsUpCount + ) + } + + onListing( + MangaListing( + itemID, + title, + thumbnail, + author, + tags, + type, + thumbsUpCount, + entries + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/viewmodel/MainViewModel.kt b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/viewmodel/MainViewModel.kt new file mode 100644 index 00000000..befdd738 --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/viewmodel/MainViewModel.kt @@ -0,0 +1,116 @@ +/* + * 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 . + */ + +package xyz.quaver.pupil.sources.manatoki.viewmodel + +import android.app.Application +import androidx.compose.runtime.mutableStateListOf +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import io.ktor.client.* +import io.ktor.client.request.* +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.launch +import kotlinx.coroutines.yield +import kotlinx.serialization.Serializable +import org.jsoup.Jsoup +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.sources.manatoki.composable.Thumbnail +import xyz.quaver.pupil.sources.manatoki.waitForRateLimit + +@Serializable +data class TopWeekly( + val itemID: String, + val title: String, + 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() + + val recentUpload = mutableStateListOf() + val mangaList = mutableStateListOf() + val topWeekly = mutableStateListOf() + + private var loadJob: Job? = null + + fun load() { + viewModelScope.launch { + loadJob?.cancelAndJoin() + recentUpload.clear() + mangaList.clear() + topWeekly.clear() + + loadJob = launch { + runCatching { + waitForRateLimit() + val doc = Jsoup.parse(client.get("https://manatoki116.net/")) + + yield() + + val misoPostGallery = doc.select(".miso-post-gallery") + + misoPostGallery[0] + .select(".post-image > a") + .forEach { entry -> + val itemID = entry.attr("href").takeLastWhile { it != '/' } + val title = entry.selectFirst("div.in-subject > b")!!.ownText() + val thumbnail = entry.selectFirst("img")!!.attr("src") + + yield() + recentUpload.add(Thumbnail(itemID, title, thumbnail)) + } + + misoPostGallery[1] + .select(".post-image > a").also { logger.info { it.size.toString() } } + .forEach { entry -> + val itemID = entry.attr("href").takeLastWhile { it != '/' } + val title = entry.selectFirst("div.in-subject")!!.ownText() + val thumbnail = entry.selectFirst("img")!!.attr("src") + + yield() + mangaList.add(Thumbnail(itemID, title, thumbnail)) + } + + val misoPostList = doc.select(".miso-post-list") + + misoPostList[4] + .select(".post-row > a") + .forEach { entry -> + yield() + val itemID = entry.attr("href").takeLastWhile { it != '/' } + val title = entry.ownText() + val count = entry.selectFirst("span.count")!!.text() + topWeekly.add(TopWeekly(itemID, title, count)) + } + }.onFailure { + logger.warning(it) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt index 4f6ef99f..d70c97cf 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -22,10 +22,16 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.graphics.Color import androidx.core.view.WindowCompat import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.google.accompanist.insets.ProvideWindowInsets +import com.google.accompanist.systemuicontroller.rememberSystemUiController import org.kodein.di.DIAware import org.kodein.di.android.closestDI import org.kodein.di.instance @@ -53,9 +59,34 @@ class MainActivity : ComponentActivity(), DIAware { ProvideWindowInsets { val navController = rememberNavController() - NavHost(navController, startDestination = "hitomi.la") { + val systemUiController = rememberSystemUiController() + val useDarkIcons = MaterialTheme.colors.isLight + + SideEffect { + systemUiController.setSystemBarsColor( + color = Color.Transparent, + darkIcons = useDarkIcons + ) + } + + NavHost(navController, startDestination = "main") { + composable("main") { + var launched by rememberSaveable { mutableStateOf(false) } + + LaunchedEffect(Unit) { + if (!launched) { + val source = it.arguments?.getString("source") ?: "hitomi.la" + navController.navigate(source) + launched = true + } else { + onBackPressed() + } + } + } sources.forEach { - it.second.run { navGraph(navController) } + it.second.run { + navGraph(navController) + } } } } diff --git a/app/src/main/java/xyz/quaver/pupil/util/misc.kt b/app/src/main/java/xyz/quaver/pupil/util/misc.kt index 721c8940..ffc9cf62 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/misc.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/misc.kt @@ -18,6 +18,9 @@ package xyz.quaver.pupil.util +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper import android.graphics.BitmapFactory import android.view.View import androidx.compose.runtime.Composable @@ -82,3 +85,15 @@ fun rememberFileXImageSource(file: FileX) = remember { fun sha256(data: ByteArray) : ByteArray { return MessageDigest.getInstance("SHA-256").digest(data) } + +val Context.activity: Activity? + get() { + var currentContext = this + while (currentContext is ContextWrapper) { + if (currentContext is Activity) + return currentContext + currentContext = currentContext.baseContext + } + + return null + } \ No newline at end of file