diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index e9969a1c..a7943627 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -12,6 +12,7 @@
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 1c3fccd4..8d7d9485 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -49,7 +49,7 @@ android {
compose = true
}
composeOptions {
- kotlinCompilerExtensionVersion = "1.0.5"
+ kotlinCompilerExtensionVersion = Versions.JETPACK_COMPOSE
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
@@ -75,23 +75,24 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2-native-mt")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1")
- implementation("androidx.compose.ui:ui:1.0.5")
- implementation("androidx.compose.ui:ui-tooling:1.0.5")
- implementation("androidx.compose.foundation:foundation:1.0.5")
- implementation("androidx.compose.material:material:1.0.5")
- implementation("androidx.compose.material:material-icons-extended:1.0.5")
- implementation("androidx.compose.runtime:runtime-livedata:1.0.5")
- implementation("androidx.compose.ui:ui-util:1.0.5")
- implementation("androidx.compose.animation:animation:1.1.0-rc01")
implementation("androidx.activity:activity-compose:1.4.0")
implementation("androidx.navigation:navigation-compose:2.4.0-rc01")
- implementation("com.google.accompanist:accompanist-flowlayout:0.20.3")
- implementation("com.google.accompanist:accompanist-appcompat-theme:0.20.3")
- implementation("com.google.accompanist:accompanist-insets:0.20.3")
- implementation("com.google.accompanist:accompanist-insets-ui:0.20.3")
- implementation("com.google.accompanist:accompanist-drawablepainter:0.20.3")
- implementation("com.google.accompanist:accompanist-systemuicontroller:0.20.3")
+ implementation(JetpackCompose.FOUNDATION)
+ implementation(JetpackCompose.UI)
+ implementation(JetpackCompose.UI_UTIL)
+ implementation(JetpackCompose.UI_TOOLING)
+ implementation(JetpackCompose.ANIMATION)
+ implementation(JetpackCompose.MATERIAL)
+ implementation(JetpackCompose.MATERIAL_ICONS)
+ implementation(JetpackCompose.RUNTIME_LIVEDATA)
+
+ implementation(Accompanist.INSETS)
+ implementation(Accompanist.INSETS_UI)
+ implementation(Accompanist.FLOW_LAYOUT)
+ implementation(Accompanist.SYSTEM_UI_CONTROLLER)
+ implementation(Accompanist.DRAWABLE_PAINTER)
+ implementation(Accompanist.APPCOMPAT_THEME)
implementation("io.coil-kt:coil-compose:1.4.0")
@@ -104,8 +105,6 @@ dependencies {
implementation("androidx.fragment:fragment-ktx:1.4.0")
implementation("androidx.preference:preference-ktx:1.1.1")
implementation("androidx.recyclerview:recyclerview:1.2.1")
- implementation("androidx.constraintlayout:constraintlayout:2.1.2")
- implementation("androidx.gridlayout:gridlayout:1.0.0")
implementation("androidx.biometric:biometric:1.1.0")
implementation("androidx.work:work-runtime-ktx:2.7.1")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.0")
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/composable/ModalTopSheetLayout.kt b/app/src/main/java/xyz/quaver/pupil/sources/composable/ModalTopSheetLayout.kt
index 0ad7f1b6..e3580786 100644
--- a/app/src/main/java/xyz/quaver/pupil/sources/composable/ModalTopSheetLayout.kt
+++ b/app/src/main/java/xyz/quaver/pupil/sources/composable/ModalTopSheetLayout.kt
@@ -18,6 +18,7 @@
package xyz.quaver.pupil.sources.composable
+import android.util.Log
import androidx.compose.animation.core.TweenSpec
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas
@@ -184,18 +185,13 @@ fun ModalTopSheetLayout(
available: Offset,
source: NestedScrollSource
): Offset {
- return if (drawerState.offset.value < 0f && source == NestedScrollSource.Drag)
+ return if (source == NestedScrollSource.Drag)
Offset(0f, drawerState.performDrag(available.y))
else
Offset.Zero
}
- override suspend fun onPreFling(available: Velocity): Velocity {
- val toFling = available.y
- return if (toFling > 0 && drawerState.offset.value < 0f) {
- available
- } else Velocity.Zero
- }
+ override suspend fun onPreFling(available: Velocity): Velocity = Velocity.Zero
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
drawerState.performFling(available.y)
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 6a0d8104..1a17ca4d 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
@@ -92,7 +92,6 @@ import xyz.quaver.pupil.ui.theme.Orange500
import kotlin.math.max
import kotlin.math.sign
-private val imageUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Mobile Safari/537.36"
@OptIn(
ExperimentalMaterialApi::class,
@@ -105,8 +104,6 @@ class Manatoki(app: Application) : Source(), DIAware {
private val logger = newLogger(LoggerFactory.default)
- private val client: HttpClient by instance()
-
override val name = "manatoki.net"
override val iconResID = R.drawable.manatoki
@@ -119,819 +116,4 @@ class Manatoki(app: Application) : Source(), DIAware {
}
}
- @Composable
- fun Main(navController: NavController) {
- val model: MainViewModel = viewModel()
-
- 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 {
- it.toBuilder()
- .setRecentSource(name)
- .build()
- }
- }
-
- 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)
- SourceSelectDialog(navController, name) { sourceSelectDialog = false }
-
- LaunchedEffect(Unit) {
- 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
- )
- )
- },
- floatingActionButton = {
- FloatingActionButton(
- modifier = Modifier.navigationBarsPadding(),
- onClick = {
- navController.navigate("manatoki.net/search")
- }
- ) {
- Icon(
- Icons.Default.Search,
- contentDescription = null
- )
- }
- }
- ) { contentPadding ->
- Box(Modifier.padding(contentPadding)) {
- Column(
- Modifier
- .padding(8.dp, 0.dp)
- .verticalScroll(rememberScrollState()),
- verticalArrangement = Arrangement.spacedBy(16.dp)
- ) {
- 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(model.recentUpload) { item ->
- Thumbnail(item,
- Modifier
- .width(180.dp)
- .aspectRatio(6 / 7f)) {
- coroutineScope.launch {
- mangaListing = null
- sheetState.show()
- }
- 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))
- }
- }
-
- Row(
- modifier = Modifier.fillMaxWidth(),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceBetween
- ) {
- Text("만화 목록", style = MaterialTheme.typography.h5)
-
- IconButton(onClick = { navController.navigate("manatoki.net/search") }) {
- Icon(
- Icons.Default.Add,
- contentDescription = null
- )
- }
- }
- LazyRow(
- modifier = Modifier
- .fillMaxWidth()
- .height(210.dp),
- horizontalArrangement = Arrangement.spacedBy(16.dp)
- ) {
- items(model.mangaList) { item ->
- Thumbnail(item,
- Modifier
- .width(180.dp)
- .aspectRatio(6f / 7)) {
- coroutineScope.launch {
- mangaListing = null
- sheetState.show()
- }
- 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 ->
- Card(
- modifier = Modifier.clickable {
- coroutineScope.launch {
- mangaListing = null
- sheetState.show()
- }
-
- coroutineScope.launch {
- client.getItem(item.itemID, onListing, onReader)
- }
- }
- ) {
- Row(
- modifier = Modifier.height(IntrinsicSize.Min),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(8.dp)
- ) {
- Box(
- modifier = Modifier
- .background(Color(0xFF64C3F5))
- .width(24.dp)
- .fillMaxHeight(),
- contentAlignment = Alignment.Center
- ) {
- Text(
- (index + 1).toString(),
- color = Color.White,
- textAlign = TextAlign.Center
- )
- }
-
- Text(
- item.title,
- modifier = Modifier
- .weight(1f)
- .padding(0.dp, 4.dp),
- 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")
- var readerInfo: ReaderInfo? by rememberSaveable { mutableStateOf(null) }
-
- LaunchedEffect(Unit) {
- if (itemID != null)
- client.getItem(itemID, onReader = {
- readerInfo = it
- model.load(it.urls) {
- set("User-Agent", imageUserAgent)
- }
- })
- else model.error = true
- }
-
- val bookmark by bookmarkDao.contains(name, itemID ?: "").observeAsState(false)
-
- val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
- var mangaListing: MangaListing? by rememberSaveable { mutableStateOf(null) }
- val mangaListingRippleInteractionSource = remember { mutableStateListOf() }
- val navigationBarsPadding = LocalDensity.current.run {
- rememberInsetsPaddingValues(
- LocalWindowInsets.current.navigationBars
- ).calculateBottomPadding().toPx()
- }
-
- val listState = rememberLazyListState()
-
- var scrollDirection by remember { mutableStateOf(0f) }
-
- BackHandler {
- when {
- sheetState.isVisible -> coroutineScope.launch { sheetState.hide() }
- model.fullscreen -> model.fullscreen = false
- else -> navController.popBackStack()
- }
- }
-
- var mangaListingListSize: Size? by remember { mutableStateOf(null) }
-
- ModalBottomSheetLayout(
- sheetState = sheetState,
- sheetShape = RoundedCornerShape(32.dp, 32.dp, 0.dp, 0.dp),
- sheetContent = {
- MangaListingBottomSheet(
- mangaListing,
- currentItemID = itemID,
- onListSize = {
- mangaListingListSize = it
- },
- rippleInteractionSource = mangaListingRippleInteractionSource,
- listState = listState
- ) {
- coroutineScope.launch {
- client.getItem(
- it,
- onReader = {
- navController.navigate("manatoki.net/reader/${it.itemID}") {
- popUpTo("manatoki.net/")
- }
- }
- )
- }
- }
- }
- ) {
- Scaffold(
- topBar = {
- if (!model.fullscreen)
- TopAppBar(
- title = {
- Text(
- readerInfo?.title ?: stringResource(R.string.reader_loading),
- maxLines = 1,
- overflow = TextOverflow.Ellipsis
- )
- },
- navigationIcon = {
- IconButton(onClick = { navController.navigateUp() }) {
- Icon(
- Icons.Default.NavigateBefore,
- contentDescription = null
- )
- }
- },
- actions = {
- IconButton({ }) {
- Image(
- painter = painterResource(R.drawable.manatoki),
- contentDescription = null,
- modifier = Modifier.size(24.dp)
- )
- }
-
- IconButton(onClick = {
- itemID?.let {
- coroutineScope.launch {
- if (bookmark) bookmarkDao.delete(name, it)
- else bookmarkDao.insert(name, it)
- }
- }
- }) {
- Icon(
- if (bookmark) Icons.Default.Star else Icons.Default.StarOutline,
- contentDescription = null,
- tint = Orange500
- )
- }
- },
- contentPadding = rememberInsetsPaddingValues(
- LocalWindowInsets.current.statusBars,
- applyBottom = false
- )
- )
- },
- floatingActionButton = {
- AnimatedVisibility(
- !(model.fullscreen || scrollDirection < 0f),
- enter = scaleIn(),
- exit = scaleOut()
- ) {
- FloatingActionButton(
- modifier = Modifier.navigationBarsPadding(),
- onClick = {
- readerInfo?.let {
- coroutineScope.launch {
- sheetState.show()
- }
-
- coroutineScope.launch {
- if (mangaListing?.itemID != it.listingItemID)
- client.getItem(it.listingItemID, onListing = {
- mangaListing = it
-
- mangaListingRippleInteractionSource.addAll(
- List(max(it.entries.size - mangaListingRippleInteractionSource.size, 0)) {
- MutableInteractionSource()
- }
- )
-
- coroutineScope.launch {
- while (listState.layoutInfo.totalItemsCount != it.entries.size) {
- delay(100)
- }
-
- val targetIndex = it.entries.indexOfFirst { it.itemID == itemID }
-
- listState.scrollToItem(targetIndex)
-
- mangaListingListSize?.let { sheetSize ->
- val targetItem = listState.layoutInfo.visibleItemsInfo.first {
- it.key == itemID
- }
-
- if (targetItem.offset == 0) {
- listState.animateScrollBy(
- -(sheetSize.height - navigationBarsPadding - targetItem.size)
- )
- }
-
- delay(200)
-
- with (mangaListingRippleInteractionSource[targetIndex]) {
- val interaction = PressInteraction.Press(
- Offset(sheetSize.width/2, targetItem.size/2f)
- )
-
- emit(interaction)
- emit(PressInteraction.Release(interaction))
- }
- }
- }
- })
- }
-
- }
- }
- ) {
- Icon(
- Icons.Default.List,
-
- contentDescription = null
- )
- }
- }
- }
- ) { contentPadding ->
- ReaderBase(
- Modifier.padding(contentPadding),
- model = model,
- onScroll = { scrollDirection = it }
- )
- }
- }
- }
-
- @Composable
- fun Recent(navController: NavController) {
- val model: RecentViewModel = viewModel()
- 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()
- }
-
- 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()
- }
- ) {
- 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
- })
- }
- }
- }
- }
-
- if (model.loading)
- CircularProgressIndicator(Modifier.align(Alignment.Center))
- }
- }
- }
- }
- }
- }
-
- @Composable
- fun Search(navController: NavController) {
- val model: SearchViewModel = viewModel()
-
- 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
-
- LaunchedEffect(Unit) {
- model.search()
- }
-
- BackHandler {
- when {
- sheetState.isVisible -> coroutineScope.launch { sheetState.hide() }
- drawerState.currentValue != ModalTopSheetState.Hidden ->
- coroutineScope.launch { drawerState.animateTo(ModalTopSheetState.Hidden) }
- else -> navController.popBackStack()
- }
- }
-
- ModalBottomSheetLayout(
- sheetState = sheetState,
- sheetShape = RoundedCornerShape(32.dp, 32.dp, 0.dp, 0.dp),
- sheetContent = {
- MangaListingBottomSheet(mangaListing) {
- coroutineScope.launch {
- client.getItem(it, onReader = {
- launch {
- sheetState.snapTo(ModalBottomSheetValue.Hidden)
- navController.navigate("manatoki.net/reader/${it.itemID}")
- }
- })
- }
- }
- }
- ) {
- Scaffold(
- modifier = Modifier
- .pointerInput(Unit) {
- detectTapGestures { focusManager.clearFocus() }
- },
- topBar = {
- TopAppBar(
- title = {
- TextField(
- model.stx,
- modifier = Modifier
- .onFocusChanged {
- searchFocused = it.isFocused
- }
- .fillMaxWidth(),
- onValueChange = { model.stx = it },
- placeholder = { Text("제목") },
- textStyle = MaterialTheme.typography.subtitle1,
- singleLine = true,
- trailingIcon = {
- if (model.stx != "" && searchFocused)
- IconButton(onClick = { model.stx = "" }) {
- Icon(
- Icons.Default.Close,
- contentDescription = null,
- tint = contentColorFor(MaterialTheme.colors.primarySurface)
- )
- }
- },
- keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
- keyboardActions = KeyboardActions(
- onSearch = {
- focusManager.clearFocus()
- coroutineScope.launch {
- drawerState.animateTo(ModalTopSheetState.Hidden)
- }
- coroutineScope.launch {
- model.search()
- }
- }
- ),
- colors = TextFieldDefaults.textFieldColors(
- textColor = contentColorFor(MaterialTheme.colors.primarySurface),
- placeholderColor = contentColorFor(MaterialTheme.colors.primarySurface).copy(alpha = 0.75f),
- backgroundColor = Color.Transparent,
- cursorColor = MaterialTheme.colors.secondary,
- disabledIndicatorColor = Color.Transparent,
- focusedIndicatorColor = Color.Transparent,
- errorIndicatorColor = Color.Transparent,
- unfocusedIndicatorColor = Color.Transparent
- )
- )
- },
- navigationIcon = {
- IconButton(onClick = { navController.navigateUp() }) {
- Icon(
- Icons.Default.NavigateBefore,
- contentDescription = null
- )
- }
- },
- contentPadding = rememberInsetsPaddingValues(
- LocalWindowInsets.current.statusBars,
- applyBottom = false
- )
- )
- }
- ) { contentPadding ->
- Box(Modifier.padding(contentPadding)) {
- ModalTopSheetLayout(
- modifier = Modifier.run {
- if (drawerState.currentValue == ModalTopSheetState.Hidden)
- offset(0.dp, handleOffset)
- else
- navigationBarsWithImePadding()
- },
- drawerState = drawerState,
- drawerContent = {
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .padding(16.dp, 0.dp)
- .verticalScroll(rememberScrollState()),
- verticalArrangement = Arrangement.spacedBy(8.dp)
- ) {
- Text("작가")
- TextField(model.artist, onValueChange = { model.artist = it })
-
- Text("발행")
- FlowRow(mainAxisSpacing = 4.dp, crossAxisSpacing = 4.dp) {
- Chip("전체", model.publish.isEmpty()) {
- model.publish = ""
- }
- availablePublish.forEach {
- Chip(it, model.publish == it) {
- model.publish = it
- }
- }
- }
-
- Text("초성")
- FlowRow(mainAxisSpacing = 4.dp, crossAxisSpacing = 4.dp) {
- Chip("전체", model.jaum.isEmpty()) {
- model.jaum = ""
- }
- availableJaum.forEach {
- Chip(it, model.jaum == it) {
- model.jaum = it
- }
- }
- }
-
- Text("장르")
- FlowRow(mainAxisSpacing = 4.dp, crossAxisSpacing = 4.dp) {
- Chip("전체", model.tag.isEmpty()) {
- model.tag.clear()
- }
- availableTag.forEach {
- Chip(it, model.tag.contains(it)) {
- if (model.tag.contains(it))
- model.tag.remove(it)
- else
- model.tag[it] = it
- }
- }
- }
-
- Text("정렬")
- FlowRow(mainAxisSpacing = 4.dp, crossAxisSpacing = 4.dp) {
- Chip("기본", model.sst.isEmpty()) {
- model.sst = ""
- }
- availableSst.entries.forEach { (k, v) ->
- Chip(v, model.sst == k) {
- model.sst = k
- }
- }
- }
-
- Box(
- Modifier
- .fillMaxWidth()
- .height(8.dp))
- }
- }
- ) {
- OverscrollPager(
- currentPage = model.page,
- prevPageAvailable = model.page > 1,
- nextPageAvailable = model.page < model.maxPage,
- nextPageTurnIndicatorOffset = rememberInsetsPaddingValues(
- LocalWindowInsets.current.navigationBars
- ).calculateBottomPadding(),
- onPageTurn = {
- model.page = it
- coroutineScope.launch {
- model.search(resetPage = false)
- }
- }
- ) {
- Box(Modifier.fillMaxSize()) {
- LazyVerticalGrid(
- GridCells.Adaptive(minSize = 200.dp),
- contentPadding = rememberInsetsPaddingValues(
- LocalWindowInsets.current.navigationBars
- )
- ) {
- items(model.result) { item ->
- Thumbnail(
- Thumbnail(item.itemID, item.title, item.thumbnail),
- modifier = Modifier
- .fillMaxWidth()
- .aspectRatio(3f / 4)
- .padding(8.dp)
- ) {
- coroutineScope.launch {
- mangaListing = null
- sheetState.show()
- }
- coroutineScope.launch {
- client.getItem(it, onListing = {
- mangaListing = it
- })
- }
- }
- }
- }
-
- if (model.loading)
- CircularProgressIndicator(Modifier.align(Alignment.Center))
- }
- }
- }
- }
- }
- }
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Main.kt b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Main.kt
new file mode 100644
index 00000000..e1995e75
--- /dev/null
+++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Main.kt
@@ -0,0 +1,326 @@
+/*
+ * 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.activity.compose.BackHandler
+import androidx.compose.foundation.*
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyRow
+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.Add
+import androidx.compose.material.icons.filled.Search
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.runtime.*
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import com.google.accompanist.insets.LocalWindowInsets
+import com.google.accompanist.insets.navigationBarsPadding
+import com.google.accompanist.insets.rememberInsetsPaddingValues
+import com.google.accompanist.insets.ui.Scaffold
+import com.google.accompanist.insets.ui.TopAppBar
+import io.ktor.client.*
+import kotlinx.coroutines.launch
+import org.kodein.di.compose.rememberInstance
+import xyz.quaver.pupil.R
+import xyz.quaver.pupil.proto.settingsDataStore
+import xyz.quaver.pupil.sources.composable.SourceSelectDialog
+import xyz.quaver.pupil.sources.manatoki.MangaListing
+import xyz.quaver.pupil.sources.manatoki.ReaderInfo
+import xyz.quaver.pupil.sources.manatoki.getItem
+import xyz.quaver.pupil.sources.manatoki.viewmodel.MainViewModel
+
+@ExperimentalMaterialApi
+@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
+ }
+
+ val context = LocalContext.current
+ LaunchedEffect(Unit) {
+ context.settingsDataStore.updateData {
+ it.toBuilder()
+ .setRecentSource("manatoki.net")
+ .build()
+ }
+ }
+
+ 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)
+ SourceSelectDialog(navController, "manatoki.net") { sourceSelectDialog = false }
+
+ LaunchedEffect(Unit) {
+ 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
+ )
+ )
+ },
+ floatingActionButton = {
+ FloatingActionButton(
+ modifier = Modifier.navigationBarsPadding(),
+ onClick = {
+ navController.navigate("manatoki.net/search")
+ }
+ ) {
+ Icon(
+ Icons.Default.Search,
+ contentDescription = null
+ )
+ }
+ }
+ ) { contentPadding ->
+ Box(Modifier.padding(contentPadding)) {
+ Column(
+ Modifier
+ .padding(8.dp, 0.dp)
+ .verticalScroll(rememberScrollState()),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ 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(model.recentUpload) { item ->
+ Thumbnail(item,
+ Modifier
+ .width(180.dp)
+ .aspectRatio(6 / 7f)) {
+ coroutineScope.launch {
+ mangaListing = null
+ sheetState.show()
+ }
+ 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))
+ }
+ }
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text("만화 목록", style = MaterialTheme.typography.h5)
+
+ IconButton(onClick = { navController.navigate("manatoki.net/search") }) {
+ Icon(
+ Icons.Default.Add,
+ contentDescription = null
+ )
+ }
+ }
+ LazyRow(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(210.dp),
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ items(model.mangaList) { item ->
+ Thumbnail(item,
+ Modifier
+ .width(180.dp)
+ .aspectRatio(6f / 7)) {
+ coroutineScope.launch {
+ mangaListing = null
+ sheetState.show()
+ }
+ 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 ->
+ Card(
+ modifier = Modifier.clickable {
+ coroutineScope.launch {
+ mangaListing = null
+ sheetState.show()
+ }
+
+ coroutineScope.launch {
+ client.getItem(item.itemID, onListing, onReader)
+ }
+ }
+ ) {
+ Row(
+ modifier = Modifier.height(IntrinsicSize.Min),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Box(
+ modifier = Modifier
+ .background(Color(0xFF64C3F5))
+ .width(24.dp)
+ .fillMaxHeight(),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ (index + 1).toString(),
+ color = Color.White,
+ textAlign = TextAlign.Center
+ )
+ }
+
+ Text(
+ item.title,
+ modifier = Modifier
+ .weight(1f)
+ .padding(0.dp, 4.dp),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+
+ Text(
+ item.count,
+ color = Color(0xFFFF4500)
+ )
+ }
+ }
+ }
+ }
+
+ Box(Modifier.navigationBarsPadding())
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Reader.kt b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Reader.kt
new file mode 100644
index 00000000..ac93b52d
--- /dev/null
+++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Reader.kt
@@ -0,0 +1,284 @@
+/*
+ * 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.activity.compose.BackHandler
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.scaleIn
+import androidx.compose.animation.scaleOut
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.gestures.animateScrollBy
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.List
+import androidx.compose.material.icons.filled.NavigateBefore
+import androidx.compose.material.icons.filled.Star
+import androidx.compose.material.icons.filled.StarOutline
+import androidx.compose.runtime.*
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import com.google.accompanist.insets.LocalWindowInsets
+import com.google.accompanist.insets.navigationBarsPadding
+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 xyz.quaver.pupil.R
+import xyz.quaver.pupil.db.AppDatabase
+import xyz.quaver.pupil.sources.composable.ReaderBase
+import xyz.quaver.pupil.sources.composable.ReaderBaseViewModel
+import xyz.quaver.pupil.sources.manatoki.MangaListing
+import xyz.quaver.pupil.sources.manatoki.ReaderInfo
+import xyz.quaver.pupil.sources.manatoki.getItem
+import xyz.quaver.pupil.ui.theme.Orange500
+import kotlin.math.max
+
+private val imageUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Mobile Safari/537.36"
+
+@ExperimentalAnimationApi
+@ExperimentalFoundationApi
+@ExperimentalMaterialApi
+@ExperimentalComposeUiApi
+@Composable
+fun Reader(navController: NavController) {
+ val model: ReaderBaseViewModel = viewModel()
+
+ val client: HttpClient by rememberInstance()
+
+ val database: AppDatabase by rememberInstance()
+ val bookmarkDao = database.bookmarkDao()
+
+ val coroutineScope = rememberCoroutineScope()
+
+ val itemID = navController.currentBackStackEntry?.arguments?.getString("itemID")
+ var readerInfo: ReaderInfo? by rememberSaveable { mutableStateOf(null) }
+
+ LaunchedEffect(Unit) {
+ if (itemID != null)
+ client.getItem(itemID, onReader = {
+ readerInfo = it
+ model.load(it.urls) {
+ set("User-Agent", imageUserAgent)
+ }
+ })
+ else model.error = true
+ }
+
+ val bookmark by bookmarkDao.contains("manatoki.net", itemID ?: "").observeAsState(false)
+
+ val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
+ var mangaListing: MangaListing? by rememberSaveable { mutableStateOf(null) }
+ val mangaListingRippleInteractionSource = remember { mutableStateListOf() }
+ val navigationBarsPadding = LocalDensity.current.run {
+ rememberInsetsPaddingValues(
+ LocalWindowInsets.current.navigationBars
+ ).calculateBottomPadding().toPx()
+ }
+
+ val listState = rememberLazyListState()
+
+ var scrollDirection by remember { mutableStateOf(0f) }
+
+ BackHandler {
+ when {
+ sheetState.isVisible -> coroutineScope.launch { sheetState.hide() }
+ model.fullscreen -> model.fullscreen = false
+ else -> navController.popBackStack()
+ }
+ }
+
+ var mangaListingListSize: Size? by remember { mutableStateOf(null) }
+
+ ModalBottomSheetLayout(
+ sheetState = sheetState,
+ sheetShape = RoundedCornerShape(32.dp, 32.dp, 0.dp, 0.dp),
+ sheetContent = {
+ MangaListingBottomSheet(
+ mangaListing,
+ currentItemID = itemID,
+ onListSize = {
+ mangaListingListSize = it
+ },
+ rippleInteractionSource = mangaListingRippleInteractionSource,
+ listState = listState
+ ) {
+ coroutineScope.launch {
+ client.getItem(
+ it,
+ onReader = {
+ navController.navigate("manatoki.net/reader/${it.itemID}") {
+ popUpTo("manatoki.net/")
+ }
+ }
+ )
+ }
+ }
+ }
+ ) {
+ Scaffold(
+ topBar = {
+ if (!model.fullscreen)
+ TopAppBar(
+ title = {
+ Text(
+ readerInfo?.title ?: stringResource(R.string.reader_loading),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ },
+ navigationIcon = {
+ IconButton(onClick = { navController.navigateUp() }) {
+ Icon(
+ Icons.Default.NavigateBefore,
+ contentDescription = null
+ )
+ }
+ },
+ actions = {
+ IconButton({ }) {
+ Image(
+ painter = painterResource(R.drawable.manatoki),
+ contentDescription = null,
+ modifier = Modifier.size(24.dp)
+ )
+ }
+
+ IconButton(onClick = {
+ itemID?.let {
+ coroutineScope.launch {
+ if (bookmark) bookmarkDao.delete("manatoki.net", it)
+ else bookmarkDao.insert("manatoki.net", it)
+ }
+ }
+ }) {
+ Icon(
+ if (bookmark) Icons.Default.Star else Icons.Default.StarOutline,
+ contentDescription = null,
+ tint = Orange500
+ )
+ }
+ },
+ contentPadding = rememberInsetsPaddingValues(
+ LocalWindowInsets.current.statusBars,
+ applyBottom = false
+ )
+ )
+ },
+ floatingActionButton = {
+ AnimatedVisibility(
+ !(model.fullscreen || scrollDirection < 0f),
+ enter = scaleIn(),
+ exit = scaleOut()
+ ) {
+ FloatingActionButton(
+ modifier = Modifier.navigationBarsPadding(),
+ onClick = {
+ readerInfo?.let {
+ coroutineScope.launch {
+ sheetState.show()
+ }
+
+ coroutineScope.launch {
+ if (mangaListing?.itemID != it.listingItemID)
+ client.getItem(it.listingItemID, onListing = {
+ mangaListing = it
+
+ mangaListingRippleInteractionSource.addAll(
+ List(max(it.entries.size - mangaListingRippleInteractionSource.size, 0)) {
+ MutableInteractionSource()
+ }
+ )
+
+ coroutineScope.launch {
+ while (listState.layoutInfo.totalItemsCount != it.entries.size) {
+ delay(100)
+ }
+
+ val targetIndex = it.entries.indexOfFirst { it.itemID == itemID }
+
+ listState.scrollToItem(targetIndex)
+
+ mangaListingListSize?.let { sheetSize ->
+ val targetItem = listState.layoutInfo.visibleItemsInfo.first {
+ it.key == itemID
+ }
+
+ if (targetItem.offset == 0) {
+ listState.animateScrollBy(
+ -(sheetSize.height - navigationBarsPadding - targetItem.size)
+ )
+ }
+
+ delay(200)
+
+ with (mangaListingRippleInteractionSource[targetIndex]) {
+ val interaction = PressInteraction.Press(
+ Offset(sheetSize.width/2, targetItem.size/2f)
+ )
+
+ emit(interaction)
+ emit(PressInteraction.Release(interaction))
+ }
+ }
+ }
+ })
+ }
+
+ }
+ }
+ ) {
+ Icon(
+ Icons.Default.List,
+
+ contentDescription = null
+ )
+ }
+ }
+ }
+ ) { contentPadding ->
+ ReaderBase(
+ Modifier.padding(contentPadding),
+ model = model,
+ onScroll = { scrollDirection = it }
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Recent.kt b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Recent.kt
new file mode 100644
index 00000000..58350a82
--- /dev/null
+++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Recent.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.activity.compose.BackHandler
+import androidx.compose.foundation.ExperimentalFoundationApi
+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.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
+import com.google.accompanist.insets.ui.Scaffold
+import com.google.accompanist.insets.ui.TopAppBar
+import io.ktor.client.*
+import kotlinx.coroutines.launch
+import org.kodein.di.compose.rememberInstance
+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
+
+@ExperimentalFoundationApi
+@ExperimentalMaterialApi
+@Composable
+fun Recent(navController: NavController) {
+ val model: RecentViewModel = viewModel()
+
+ val client: HttpClient by rememberInstance()
+
+ 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()
+ }
+
+ 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()
+ }
+ ) {
+ 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
+ })
+ }
+ }
+ }
+ }
+
+ if (model.loading)
+ CircularProgressIndicator(Modifier.align(Alignment.Center))
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Search.kt b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Search.kt
new file mode 100644
index 00000000..ac269df9
--- /dev/null
+++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Search.kt
@@ -0,0 +1,340 @@
+/*
+ * 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.util.Log
+import androidx.activity.compose.BackHandler
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.detectTapGestures
+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.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.NavigateBefore
+import androidx.compose.runtime.*
+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.graphics.Color
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import com.google.accompanist.flowlayout.FlowRow
+import com.google.accompanist.insets.LocalWindowInsets
+import com.google.accompanist.insets.navigationBarsWithImePadding
+import com.google.accompanist.insets.rememberInsetsPaddingValues
+import com.google.accompanist.insets.ui.Scaffold
+import com.google.accompanist.insets.ui.TopAppBar
+import io.ktor.client.*
+import kotlinx.coroutines.launch
+import org.kodein.di.compose.rememberInstance
+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.viewmodel.*
+
+@ExperimentalFoundationApi
+@ExperimentalMaterialApi
+@Composable
+fun Search(navController: NavController) {
+ val model: SearchViewModel = viewModel()
+
+ val client: HttpClient by rememberInstance()
+
+ 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
+
+ LaunchedEffect(Unit) {
+ model.search()
+ }
+
+ BackHandler {
+ when {
+ sheetState.isVisible -> coroutineScope.launch { sheetState.hide() }
+ drawerState.currentValue != ModalTopSheetState.Hidden ->
+ coroutineScope.launch { drawerState.animateTo(ModalTopSheetState.Hidden) }
+ else -> navController.popBackStack()
+ }
+ }
+
+ ModalBottomSheetLayout(
+ sheetState = sheetState,
+ sheetShape = RoundedCornerShape(32.dp, 32.dp, 0.dp, 0.dp),
+ sheetContent = {
+ MangaListingBottomSheet(mangaListing) {
+ coroutineScope.launch {
+ client.getItem(it, onReader = {
+ launch {
+ sheetState.snapTo(ModalBottomSheetValue.Hidden)
+ navController.navigate("manatoki.net/reader/${it.itemID}")
+ }
+ })
+ }
+ }
+ }
+ ) {
+ Scaffold(
+ modifier = Modifier
+ .pointerInput(Unit) {
+ detectTapGestures { focusManager.clearFocus() }
+ },
+ topBar = {
+ TopAppBar(
+ title = {
+ TextField(
+ model.stx,
+ modifier = Modifier
+ .onFocusChanged {
+ searchFocused = it.isFocused
+ }
+ .fillMaxWidth(),
+ onValueChange = { model.stx = it },
+ placeholder = { Text("제목") },
+ textStyle = MaterialTheme.typography.subtitle1,
+ singleLine = true,
+ trailingIcon = {
+ if (model.stx != "" && searchFocused)
+ IconButton(onClick = { model.stx = "" }) {
+ Icon(
+ Icons.Default.Close,
+ contentDescription = null,
+ tint = contentColorFor(MaterialTheme.colors.primarySurface)
+ )
+ }
+ },
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
+ keyboardActions = KeyboardActions(
+ onSearch = {
+ focusManager.clearFocus()
+ coroutineScope.launch {
+ drawerState.animateTo(ModalTopSheetState.Hidden)
+ }
+ coroutineScope.launch {
+ model.search()
+ }
+ }
+ ),
+ colors = TextFieldDefaults.textFieldColors(
+ textColor = contentColorFor(MaterialTheme.colors.primarySurface),
+ placeholderColor = contentColorFor(MaterialTheme.colors.primarySurface).copy(alpha = 0.75f),
+ backgroundColor = Color.Transparent,
+ cursorColor = MaterialTheme.colors.secondary,
+ disabledIndicatorColor = Color.Transparent,
+ focusedIndicatorColor = Color.Transparent,
+ errorIndicatorColor = Color.Transparent,
+ unfocusedIndicatorColor = Color.Transparent
+ )
+ )
+ },
+ navigationIcon = {
+ IconButton(onClick = { navController.navigateUp() }) {
+ Icon(
+ Icons.Default.NavigateBefore,
+ contentDescription = null
+ )
+ }
+ },
+ contentPadding = rememberInsetsPaddingValues(
+ LocalWindowInsets.current.statusBars,
+ applyBottom = false
+ )
+ )
+ }
+ ) { contentPadding ->
+ Box(Modifier.padding(contentPadding)) {
+ ModalTopSheetLayout(
+ modifier = Modifier.run {
+ if (drawerState.currentValue == ModalTopSheetState.Hidden)
+ offset(0.dp, handleOffset)
+ else
+ navigationBarsWithImePadding()
+ },
+ drawerState = drawerState,
+ drawerContent = {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp, 0.dp)
+ .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("작가")
+ ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = !expanded }) {
+ TextField(
+ model.artist,
+ onValueChange = { model.artist = it },
+ trailingIcon = {
+ ExposedDropdownMenuDefaults.TrailingIcon(
+ expanded = expanded
+ )
+ }
+ )
+
+ ExposedDropdownMenu(expanded, onDismissRequest = { expanded = false }) {
+ suggestedArtists.forEach {
+ DropdownMenuItem(onClick = { model.artist = it; expanded = false }) {
+ Text(it)
+ }
+ }
+ }
+ }
+
+ Text("발행")
+ FlowRow(mainAxisSpacing = 4.dp, crossAxisSpacing = 4.dp) {
+ Chip("전체", model.publish.isEmpty()) {
+ model.publish = ""
+ }
+ availablePublish.forEach {
+ Chip(it, model.publish == it) {
+ model.publish = it
+ }
+ }
+ }
+
+ Text("초성")
+ FlowRow(mainAxisSpacing = 4.dp, crossAxisSpacing = 4.dp) {
+ Chip("전체", model.jaum.isEmpty()) {
+ model.jaum = ""
+ }
+ availableJaum.forEach {
+ Chip(it, model.jaum == it) {
+ model.jaum = it
+ }
+ }
+ }
+
+ Text("장르")
+ FlowRow(mainAxisSpacing = 4.dp, crossAxisSpacing = 4.dp) {
+ Chip("전체", model.tag.isEmpty()) {
+ model.tag.clear()
+ }
+ availableTag.forEach {
+ Chip(it, model.tag.contains(it)) {
+ if (model.tag.contains(it))
+ model.tag.remove(it)
+ else
+ model.tag[it] = it
+ }
+ }
+ }
+
+ Text("정렬")
+ FlowRow(mainAxisSpacing = 4.dp, crossAxisSpacing = 4.dp) {
+ Chip("기본", model.sst.isEmpty()) {
+ model.sst = ""
+ }
+ availableSst.entries.forEach { (k, v) ->
+ Chip(v, model.sst == k) {
+ model.sst = k
+ }
+ }
+ }
+
+ Box(Modifier.fillMaxWidth().height(8.dp))
+ }
+ }
+ ) {
+ OverscrollPager(
+ currentPage = model.page,
+ prevPageAvailable = model.page > 1,
+ nextPageAvailable = model.page < model.maxPage,
+ nextPageTurnIndicatorOffset = rememberInsetsPaddingValues(
+ LocalWindowInsets.current.navigationBars
+ ).calculateBottomPadding(),
+ onPageTurn = {
+ model.page = it
+ coroutineScope.launch {
+ model.search(resetPage = false)
+ }
+ }
+ ) {
+ Box(Modifier.fillMaxSize()) {
+ LazyVerticalGrid(
+ GridCells.Adaptive(minSize = 200.dp),
+ contentPadding = rememberInsetsPaddingValues(
+ LocalWindowInsets.current.navigationBars
+ )
+ ) {
+ items(model.result) { item ->
+ Thumbnail(
+ Thumbnail(item.itemID, item.title, item.thumbnail),
+ modifier = Modifier
+ .fillMaxWidth()
+ .aspectRatio(3f / 4)
+ .padding(8.dp)
+ ) {
+ coroutineScope.launch {
+ mangaListing = null
+ sheetState.show()
+ }
+ coroutineScope.launch {
+ client.getItem(it, onListing = {
+ mangaListing = it
+ })
+ }
+ }
+ }
+ }
+
+ if (model.loading)
+ CircularProgressIndicator(Modifier.align(Alignment.Center))
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ 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
index 281d185f..07d409e5 100644
--- a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/util.kt
+++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/util.kt
@@ -20,14 +20,14 @@ package xyz.quaver.pupil.sources.manatoki
import android.os.Parcelable
import androidx.collection.LruCache
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Card
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
+import androidx.compose.material.*
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.google.common.util.concurrent.RateLimiter
import io.ktor.client.*
@@ -90,13 +90,15 @@ data class ReaderInfo(
@ExperimentalMaterialApi
@Composable
fun Chip(text: String, selected: Boolean = false, onClick: () -> Unit = { }) {
- Card(
- onClick = onClick,
- backgroundColor = if (selected) MaterialTheme.colors.secondary else MaterialTheme.colors.surface,
- shape = RoundedCornerShape(8.dp),
- elevation = 4.dp
- ) {
- Text(text, modifier = Modifier.padding(4.dp))
+ CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides false) {
+ Card(
+ onClick = onClick,
+ backgroundColor = if (selected) MaterialTheme.colors.secondary else MaterialTheme.colors.surface,
+ shape = RoundedCornerShape(8.dp),
+ elevation = 4.dp
+ ) {
+ Text(text, modifier = Modifier.padding(4.dp))
+ }
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/viewmodel/SearchViewModel.kt b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/viewmodel/SearchViewModel.kt
index 451bd4c3..7b050ae3 100644
--- a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/viewmodel/SearchViewModel.kt
+++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/viewmodel/SearchViewModel.kt
@@ -140,6 +140,8 @@ class SearchViewModel(app: Application) : AndroidViewModel(app), DIAware {
var page by mutableStateOf(1)
var maxPage by mutableStateOf(0)
+ val availableArtists = mutableStateListOf()
+
var loading by mutableStateOf(false)
private set
@@ -154,6 +156,7 @@ class SearchViewModel(app: Application) : AndroidViewModel(app), DIAware {
loading = true
result.clear()
+ availableArtists.clear()
if (resetPage) page = 1
searchJob = launch {
@@ -179,6 +182,13 @@ class SearchViewModel(app: Application) : AndroidViewModel(app), DIAware {
maxPage = doc.getElementsByClass("pagination").first()!!.getElementsByTag("a").maxOf { it.text().toIntOrNull() ?: 0 }
+ doc.select("select > option").forEach {
+ val value = it.ownText()
+
+ if (value.isNotEmpty())
+ availableArtists.add(value)
+ }
+
doc.getElementsByClass("list-item").forEach {
val itemID =
it.selectFirst(".img-item > a")!!.attr("href").takeLastWhile { it != '/' }
diff --git a/build.gradle.kts b/build.gradle.kts
index cdaf60d3..f76c3139 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -7,9 +7,9 @@ buildscript {
}
dependencies {
classpath("com.android.tools.build:gradle:7.0.4")
- classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31")
- classpath("org.jetbrains.kotlin:kotlin-android-extensions:1.5.31")
- classpath("org.jetbrains.kotlin:kotlin-serialization:1.5.31")
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.KOTLIN_VERSION}")
+ classpath("org.jetbrains.kotlin:kotlin-android-extensions:${Versions.KOTLIN_VERSION}")
+ classpath("org.jetbrains.kotlin:kotlin-serialization:${Versions.KOTLIN_VERSION}")
classpath("com.google.gms:google-services:4.3.10")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
new file mode 100644
index 00000000..eb689fe8
--- /dev/null
+++ b/buildSrc/build.gradle.kts
@@ -0,0 +1,26 @@
+/*
+ * 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 .
+ */
+
+plugins {
+ `kotlin-dsl`
+}
+
+repositories {
+ mavenCentral()
+ google()
+}
\ No newline at end of file
diff --git a/buildSrc/build/pluginUnderTestMetadata/plugin-under-test-metadata.properties b/buildSrc/build/pluginUnderTestMetadata/plugin-under-test-metadata.properties
new file mode 100644
index 00000000..8e70d718
--- /dev/null
+++ b/buildSrc/build/pluginUnderTestMetadata/plugin-under-test-metadata.properties
@@ -0,0 +1 @@
+implementation-classpath=/home/tom5079/Workspace/Pupil/buildSrc/build/classes/java/main\:/home/tom5079/Workspace/Pupil/buildSrc/build/classes/groovy/main\:/home/tom5079/Workspace/Pupil/buildSrc/build/classes/kotlin/main\:/home/tom5079/Workspace/Pupil/buildSrc/build/resources/main
diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt
new file mode 100644
index 00000000..61855f26
--- /dev/null
+++ b/buildSrc/src/main/kotlin/Config.kt
@@ -0,0 +1,47 @@
+/*
+ * 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 .
+ */
+
+const val GROUP_ID = "xyz.quaver"
+const val VERSION = "6.0.0-alpha01"
+
+object Versions {
+ const val KOTLIN_VERSION = "1.6.0"
+
+ const val JETPACK_COMPOSE = "1.1.0-rc01"
+ const val ACCOMPANIST = "0.22.0-rc"
+}
+
+object JetpackCompose {
+ const val UI = "androidx.compose.ui:ui:${Versions.JETPACK_COMPOSE}"
+ const val UI_TOOLING = "androidx.compose.ui:ui-tooling:${Versions.JETPACK_COMPOSE}"
+ const val FOUNDATION = "androidx.compose.foundation:foundation:${Versions.JETPACK_COMPOSE}"
+ const val MATERIAL = "androidx.compose.material:material:${Versions.JETPACK_COMPOSE}"
+ const val MATERIAL_ICONS = "androidx.compose.material:material-icons-extended:${Versions.JETPACK_COMPOSE}"
+ const val RUNTIME_LIVEDATA = "androidx.compose.runtime:runtime-livedata:${Versions.JETPACK_COMPOSE}"
+ const val UI_UTIL = "androidx.compose.ui:ui-util:${Versions.JETPACK_COMPOSE}"
+ const val ANIMATION = "androidx.compose.animation:animation:${Versions.JETPACK_COMPOSE}"
+}
+
+object Accompanist {
+ const val FLOW_LAYOUT = "com.google.accompanist:accompanist-flowlayout:${Versions.ACCOMPANIST}"
+ const val APPCOMPAT_THEME = "com.google.accompanist:accompanist-appcompat-theme:${Versions.ACCOMPANIST}"
+ const val INSETS = "com.google.accompanist:accompanist-insets:${Versions.ACCOMPANIST}"
+ const val INSETS_UI = "com.google.accompanist:accompanist-insets-ui:${Versions.ACCOMPANIST}"
+ const val DRAWABLE_PAINTER = "com.google.accompanist:accompanist-drawablepainter:${Versions.ACCOMPANIST}"
+ const val SYSTEM_UI_CONTROLLER = "com.google.accompanist:accompanist-systemuicontroller:${Versions.ACCOMPANIST}"
+}
\ No newline at end of file