diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
deleted file mode 100644
index f0683223..00000000
--- a/.idea/deploymentTargetDropDown.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index b10aba0e..22f913d7 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -25,6 +25,7 @@
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 457a90bc..c2f87842 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -22,7 +22,8 @@ android {
}
buildTypes {
getByName("debug") {
- isDebuggable = true
+ isDebuggable = false
+ isMinifyEnabled = true
applicationIdSuffix = ".debug"
versionNameSuffix = "-DEBUG"
@@ -34,6 +35,7 @@ android {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
+ applicationIdSuffix = ".beta"
isCrunchPngs = false
@@ -47,7 +49,7 @@ android {
compose = true
}
composeOptions {
- kotlinCompilerExtensionVersion = "1.0.0"
+ kotlinCompilerExtensionVersion = "1.0.5"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
@@ -69,17 +71,16 @@ dependencies {
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-core: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.material:material-icons-extended:1.0.5")
+ implementation("androidx.compose.ui:ui-util:1.0.5")
implementation("androidx.activity:activity-compose:1.4.0")
implementation("androidx.navigation:navigation-compose:2.4.0-beta02")
- implementation("com.google.accompanist:accompanist-flowlayout:0.16.1")
- implementation("com.google.accompanist:accompanist-appcompat-theme:0.16.0")
- implementation("com.google.accompanist:accompanist-insets:0.18.0")
- implementation("com.google.accompanist:accompanist-insets-ui:0.18.0")
+ implementation("com.google.accompanist:accompanist-flowlayout:0.20.2")
+ implementation("com.google.accompanist:accompanist-appcompat-theme:0.20.2")
+ implementation("com.google.accompanist:accompanist-insets:0.20.2")
+ implementation("com.google.accompanist:accompanist-insets-ui:0.20.2")
implementation("io.coil-kt:coil-compose:1.3.2")
@@ -135,7 +136,7 @@ dependencies {
implementation("xyz.quaver:subsampledimage:0.0.1-alpha09-SNAPSHOT")
implementation("org.kodein.log:kodein-log:0.11.1")
- debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7")
+ //debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7")
testImplementation("junit:junit:4.13.2")
testImplementation("org.mockito:mockito-inline:4.1.0")
@@ -145,5 +146,9 @@ dependencies {
androidTestImplementation("androidx.test:runner:1.4.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
- androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.1.0-beta03")
+ androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.0.5")
+}
+
+task("clearAppCache") {
+ commandLine("adb", "shell", "pm", "clear", "xyz.quaver.pupil.debug")
}
\ No newline at end of file
diff --git a/app/release/app-debug.apk b/app/release/app-debug.apk
deleted file mode 100644
index a6797ed1..00000000
Binary files a/app/release/app-debug.apk and /dev/null differ
diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json
index 3c3a9e37..e1659dc1 100644
--- a/app/release/output-metadata.json
+++ b/app/release/output-metadata.json
@@ -1,18 +1,20 @@
{
- "version": 2,
+ "version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
- "applicationId": "xyz.quaver.pupil",
- "variantName": "processReleaseResources",
+ "applicationId": "xyz.quaver.pupil.beta",
+ "variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
- "versionCode": 64,
- "versionName": "5.1.7-alpha1",
+ "attributes": [],
+ "versionCode": 600,
+ "versionName": "6.0.0-alpha2",
"outputFile": "app-release.apk"
}
- ]
+ ],
+ "elementType": "File"
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/Pupil.kt b/app/src/main/java/xyz/quaver/pupil/Pupil.kt
index fc18bd43..ac1d8265 100644
--- a/app/src/main/java/xyz/quaver/pupil/Pupil.kt
+++ b/app/src/main/java/xyz/quaver/pupil/Pupil.kt
@@ -37,8 +37,10 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.ktx.Firebase
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
+import io.ktor.client.features.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
+import okhttp3.Protocol
import org.kodein.di.*
import org.kodein.di.android.x.androidXModule
import org.kodein.log.LoggerFactory
@@ -49,6 +51,7 @@ import xyz.quaver.pupil.sources.sourceModule
import xyz.quaver.pupil.util.*
import java.io.File
import java.util.*
+import java.util.concurrent.TimeUnit
class Pupil : Application(), DIAware {
@@ -62,9 +65,21 @@ class Pupil : Application(), DIAware {
bind { singleton {
HttpClient(OkHttp) {
+ engine {
+ config {
+ protocols(listOf(Protocol.HTTP_1_1))
+ }
+ }
install(JsonFeature) {
serializer = KotlinxSerializer()
}
+ install(HttpTimeout) {
+ requestTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS
+ socketTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS
+ connectTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS
+ }
+
+ BrowserUserAgent()
}
} }
}
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 e091a3ad..78dcf908 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
@@ -61,6 +61,8 @@ import org.kodein.di.DI
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.floatingsearchview.databinding.SearchSuggestionItemBinding
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.hitomi.*
@@ -120,6 +122,8 @@ class Hitomi(app: Application) : Source(), DIAware {
override val di: DI by closestDI(app)
+ private val logger = newLogger(LoggerFactory.default)
+
private val database: AppDatabase by instance()
private val bookmarkDao = database.bookmarkDao()
@@ -435,13 +439,13 @@ class Hitomi(app: Application) : Source(), DIAware {
var pageCount by remember { mutableStateOf("-") }
LaunchedEffect(itemInfo) {
- launch {
+ launch(Dispatchers.Default) {
itemInfo.getPageCount()?.run {
pageCount = "${this}P"
}
}
- launch {
+ launch(Dispatchers.Default) {
itemInfo.getGroups()?.run {
group = this
}
@@ -518,7 +522,9 @@ class Hitomi(app: Application) : Source(), DIAware {
)
}
- TagGroup(tags = itemInfo.tags)
+ key(itemInfo.tags) {
+ TagGroup(tags = itemInfo.tags)
+ }
}
}
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 72fc5e9b..0fb49925 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
@@ -19,361 +19,169 @@
package xyz.quaver.pupil.ui
import android.content.Intent
-import android.net.Uri
import android.os.Bundle
-import android.text.InputType
-import android.view.KeyEvent
-import android.view.Menu
-import android.view.MenuItem
-import android.widget.EditText
-import android.widget.ImageView
-import android.widget.TextView
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
import androidx.activity.viewModels
-import androidx.appcompat.app.AlertDialog
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.scrollable
+import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.CircularProgressIndicator
+import androidx.compose.material.Icon
+import androidx.compose.material.Scaffold
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.input.pointer.pointerInteropFilter
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.core.view.GravityCompat
-import androidx.core.view.children
-import androidx.core.widget.ImageViewCompat
-import androidx.swiperefreshlayout.widget.CircularProgressDrawable
-import com.google.android.material.navigation.NavigationView
+import androidx.compose.ui.util.fastAny
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
-import xyz.quaver.floatingsearchview.FloatingSearchView
+import org.kodein.log.LoggerFactory
+import org.kodein.log.newLogger
import xyz.quaver.pupil.*
import xyz.quaver.pupil.R
-import xyz.quaver.pupil.databinding.MainActivityBinding
-import xyz.quaver.pupil.sources.ItemInfo
import xyz.quaver.pupil.sources.Source
import xyz.quaver.pupil.types.*
-import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment
-import xyz.quaver.pupil.ui.dialog.SourceSelectDialog
+import xyz.quaver.pupil.ui.composable.FloatingActionButtonState
+import xyz.quaver.pupil.ui.composable.FloatingSearchBar
+import xyz.quaver.pupil.ui.composable.MultipleFloatingActionButton
+import xyz.quaver.pupil.ui.composable.SubFabItem
+import xyz.quaver.pupil.ui.theme.PupilTheme
import xyz.quaver.pupil.ui.view.ProgressCardView
import xyz.quaver.pupil.ui.viewmodel.MainViewModel
import xyz.quaver.pupil.util.*
import kotlin.math.*
-class MainActivity :
- BaseActivity(),
- NavigationView.OnNavigationItemSelectedListener,
- DIAware
-{
+class MainActivity : ComponentActivity(), DIAware {
override val di by closestDI()
- private lateinit var binding: MainActivityBinding
private val model: MainViewModel by viewModels()
- private var refreshOnResume = true
+ private val logger = newLogger(LoggerFactory.default)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- binding = MainActivityBinding.inflate(layoutInflater)
- binding.contents.composeView.setContent {
- val searchResults: List by model.searchResults.observeAsState(emptyList())
+ setContent {
val source: Source? by model.source.observeAsState(null)
val loading: Boolean by model.loading.observeAsState(false)
- val listState = rememberLazyListState()
+ var query by remember { mutableStateOf("") }
- LaunchedEffect(listState) {
+ var isFabExpanded by remember { mutableStateOf(FloatingActionButtonState.COLLAPSED) }
+
+ val lazyListState = rememberLazyListState()
+
+ val searchBarHeight = LocalDensity.current.run { 56.dp.roundToPx() }
+ var searchBarOffset by remember { mutableStateOf(0) }
+
+ LaunchedEffect(lazyListState) {
var lastOffset = 0
- val querySectionHeight = binding.contents.searchview.binding.querySection.root.height.toFloat()
- snapshotFlow { listState.firstVisibleItemScrollOffset }
+ snapshotFlow { lazyListState.firstVisibleItemScrollOffset }
.distinctUntilChanged()
.collect { newOffset ->
val dy = newOffset - lastOffset
lastOffset = newOffset
- binding.contents.searchview.apply {
- translationY = (translationY - dy).coerceIn(-querySectionHeight .. 0f)
- }
+ if (abs(dy) < searchBarHeight)
+ searchBarOffset = (searchBarOffset-dy).coerceIn(-searchBarHeight, 0)
}
}
- Box(Modifier.fillMaxSize()) {
- LazyColumn(Modifier.fillMaxSize(), state = listState, contentPadding = PaddingValues(0.dp, 64.dp, 0.dp, 0.dp)) {
- item(searchResults) {
- searchResults.forEach { itemInfo ->
- ProgressCardView(
- progress = 0.5f,
- onClick = {
- startActivity(
- Intent(
- this@MainActivity,
- ReaderActivity::class.java
- ).apply {
- putExtra("source", model.source.value!!.name)
- putExtra("id", itemInfo.itemID)
- })
+ PupilTheme {
+ Scaffold(
+ floatingActionButton = {
+ MultipleFloatingActionButton(
+ listOf(
+ SubFabItem(
+ Icons.Default.Block,
+ stringResource(R.string.main_fab_cancel)
+ ),
+ SubFabItem(
+ painterResource(R.drawable.ic_jump),
+ stringResource(R.string.main_jump_title)
+ ),
+ SubFabItem(
+ Icons.Default.Shuffle,
+ stringResource(R.string.main_fab_random)
+ ),
+ SubFabItem(
+ painterResource(R.drawable.numeric),
+ stringResource(R.string.main_open_gallery_by_id)
+ ),
+ ),
+ targetState = isFabExpanded,
+ onStateChanged = {
+ isFabExpanded = it
+ }
+ )
+ }
+ ) {
+ Box(Modifier.fillMaxSize()) {
+ LazyColumn(
+ Modifier.fillMaxSize(),
+ state = lazyListState,
+ contentPadding = PaddingValues(0.dp, 56.dp, 0.dp, 0.dp)
+ ) {
+ items(model.searchResults, key = { it.itemID }) { itemInfo ->
+ ProgressCardView(
+ progress = 0.5f,
+ onClick = {
+ startActivity(
+ Intent(
+ this@MainActivity,
+ ReaderActivity::class.java
+ ).apply {
+ putExtra("source", model.source.value!!.name)
+ putExtra("id", itemInfo.itemID)
+ })
+ }
+ ) {
+ source?.SearchResult(itemInfo = itemInfo)
}
- ) {
- source?.SearchResult(itemInfo = itemInfo)
}
}
- }
- }
- if (loading)
- CircularProgressIndicator(Modifier.align(Alignment.Center))
- }
- }
+ if (loading)
+ CircularProgressIndicator(Modifier.align(Alignment.Center))
- setContentView(binding.root)
-
- if (Preferences["download_folder", ""].isEmpty())
- DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog")
-
- checkUpdate(this)
-
- initView()
-
- model.query.observe(this) {
- binding.contents.searchview.binding.querySection.searchBarText.run {
- if (text?.toString() != it) setText(it, TextView.BufferType.EDITABLE)
- }
- }
-
- model.availableSortMode.observe(this) {
- binding.contents.searchview.post {
- binding.contents.searchview.binding.querySection.menuView.menuItems.findMenu(R.id.sort)?.subMenu?.apply {
- clear()
-
- it.forEachIndexed { index, sortMode ->
- add(R.id.sort_mode_group_id, index, Menu.NONE, sortMode).setOnMenuItemClickListener {
- model.setPage(1)
- model.sortModeIndex.value = it.itemId
-
- children.forEachIndexed { menuIndex, menuItem ->
- menuItem.isChecked = menuIndex == index
+ FloatingSearchBar(
+ modifier = Modifier.offset(0.dp, LocalDensity.current.run { searchBarOffset.toDp() }),
+ query = query,
+ onQueryChange = { query = it },
+ actions = {
+ Icon(
+ Icons.Default.Sort,
+ contentDescription = null,
+ modifier = Modifier.size(24.dp)
+ )
+ Icon(
+ Icons.Default.Settings,
+ contentDescription = null,
+ modifier = Modifier.size(24.dp)
+ )
}
-
- model.query()
- true
- }
- }
-
- setGroupCheckable(R.id.sort_mode_group_id, true, true)
-
- children.first().isChecked = true
- }
- }
- }
-
- model.sourceIcon.observe(this) {
- binding.contents.searchview.post {
- (binding.contents.searchview.binding.querySection.menuView.getChildAt(1) as ImageView).apply {
- ImageViewCompat.setImageTintList(this, null)
-
- setImageResource(it)
- }
- }
- }
-
- model.suggestions.observe(this) { runOnUiThread {
- binding.contents.searchview.swapSuggestions(
- if (it.isEmpty()) listOf(NoResultSuggestion(getString(R.string.main_no_result))) else it
- )
- } }
- }
-
- override fun onResume() {
- super.onResume()
- if (refreshOnResume) {
- model.query()
-
- refreshOnResume = false
- }
- }
-
- override fun onBackPressed() {
- if (binding.drawer.isDrawerOpen(GravityCompat.START))
- binding.drawer.closeDrawer(GravityCompat.START)
- else if (!model.onBackPressed())
- super.onBackPressed()
- }
-
- override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
- return when(keyCode) {
- KeyEvent.KEYCODE_VOLUME_UP -> {
- if (model.currentPage.value!! > 1) {
- runOnUiThread {
- model.prevPage()
- model.query()
+ )
}
}
-
- true
- }
- KeyEvent.KEYCODE_VOLUME_DOWN -> {
- if (model.currentPage.value!! < model.totalPages.value!!) {
- runOnUiThread {
- model.nextPage()
- model.query()
- }
- }
-
- true
- }
- else -> super.onKeyDown(keyCode, event)
- }
- }
-
- private fun initView() {
- //NavigationView
- binding.navView.setNavigationItemSelectedListener(this)
-
- with (binding.contents.cancelFab) {
- setOnClickListener {
-
}
}
-
- with (binding.contents.jumpFab) {
- setOnClickListener {
- val perPage = Preferences["per_page", "25"].toInt()
- val editText = EditText(context)
-
- AlertDialog.Builder(context).apply {
- setView(editText)
- setTitle(R.string.main_jump_title)
- setMessage(getString(
- R.string.main_jump_message,
- model.currentPage.value!!,
- ceil(model.totalPages.value!! / perPage.toDouble()).roundToInt()
- ))
-
- setPositiveButton(android.R.string.ok) { _, _ ->
- model.setPage(editText.text.toString().toIntOrNull() ?: return@setPositiveButton)
- model.query()
- }
- }.show()
- }
- }
-
- with (binding.contents.randomFab) {
- setOnClickListener {
- setImageDrawable(CircularProgressDrawable(context))
-/*
- model.random { runOnUiThread {
- GalleryDialogFragment(model.source.value!!.name, it.itemID).apply {
- onChipClickedHandler.add {
- model.setQueryAndSearch(it.toQuery())
- dismiss()
- }
- }.show(supportFragmentManager, "GalleryDialogFragment")
- } } */
- }
- }
-
- with (binding.contents.idFab) {
- setOnClickListener {
- val editText = EditText(context).apply {
- inputType = InputType.TYPE_CLASS_NUMBER
- }
-
- AlertDialog.Builder(context).apply {
- setView(editText)
- setTitle(R.string.main_open_gallery_by_id)
-
- setPositiveButton(android.R.string.ok) { _, _ ->
- val galleryID = editText.text.toString()
-/*
- GalleryDialogFragment(model.source.value!!.name, galleryID).apply {
- onChipClickedHandler.add {
- model.setQueryAndSearch(it.toQuery())
- dismiss()
- }
- }.show(supportFragmentManager, "GalleryDialogFragment")*/
- }
- }.show()
- }
- }
-
- setupSearchBar()
- // TODO: Save recent source
- }
-
- private fun setupSearchBar() {
- with (binding.contents.searchview) {
- onMenuItemClickListener = {
- onActionMenuItemSelected(it)
- }
-
- onQueryChangeListener = { _, query ->
- model.query.value = query
-
- model.suggestion()
-
- swapSuggestions(listOf(LoadingSuggestion(getText(R.string.reader_loading).toString())))
- }
-
- onSuggestionBinding = model.source.value!!::onSuggestionBind
-
- onFocusChangeListener = object: FloatingSearchView.OnFocusChangeListener {
- override fun onFocus() {
-
- }
-
- override fun onFocusCleared() {
- model.setPage(1)
- model.query()
- }
- }
-
- attachNavigationDrawerToMenuButton(this@MainActivity.binding.drawer)
- }
- }
-
- private fun onActionMenuItemSelected(item: MenuItem?) {
- when(item?.itemId) {
- R.id.main_menu_settings -> startActivity(Intent(this@MainActivity, SettingsActivity::class.java))
- R.id.source -> SourceSelectDialog().apply {
- onSourceSelectedListener = {
- model.setSourceAndReset(it)
-
- dismiss()
- }
-
- onSourceSettingsSelectedListener = {
- startActivity(Intent(this@MainActivity, SettingsActivity::class.java).putExtra(SettingsActivity.SETTINGS_EXTRA, it))
-
- refreshOnResume = true
- dismiss()
- }
- }.show(supportFragmentManager, null)
- }
- }
-
- override fun onNavigationItemSelected(item: MenuItem): Boolean {
- runOnUiThread {
- binding.drawer.closeDrawers()
-
- when(item.itemId) {
- R.id.main_drawer_home -> model.setModeAndReset(MainViewModel.MainMode.SEARCH)
- R.id.main_drawer_history -> model.setModeAndReset(MainViewModel.MainMode.HISTORY)
- R.id.main_drawer_downloads -> model.setModeAndReset(MainViewModel.MainMode.DOWNLOADS)
- R.id.main_drawer_help -> startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help))))
- R.id.main_drawer_github -> startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github))))
- R.id.main_drawer_homepage -> startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.home_page))))
- R.id.main_drawer_email -> startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.email))))
- R.id.main_drawer_kakaotalk -> startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.discord))))
- }
- }
-
- return true
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt
index 571d7bab..316b4b7c 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt
@@ -59,6 +59,7 @@ import xyz.quaver.pupil.R
import xyz.quaver.pupil.ui.composable.FloatingActionButtonState
import xyz.quaver.pupil.ui.composable.MultipleFloatingActionButton
import xyz.quaver.pupil.ui.composable.SubFabItem
+import xyz.quaver.pupil.ui.theme.PupilTheme
import xyz.quaver.pupil.ui.viewmodel.ReaderViewModel
import xyz.quaver.pupil.util.FileXImageSource
import kotlin.math.abs
@@ -86,7 +87,7 @@ class ReaderActivity : ComponentActivity(), DIAware {
val imageHeights = remember { mutableStateListOf() }
val states = remember { mutableStateListOf() }
- LaunchedEffect(model.totalProgress) {
+ LaunchedEffect(model.progressList.sum()) {
if (imageSources.isEmpty() && model.imageList.isNotEmpty())
imageSources.addAll(List(model.imageList.size) { null })
@@ -101,7 +102,8 @@ class ReaderActivity : ComponentActivity(), DIAware {
CoroutineScope(Dispatchers.Default).launch {
imageSources[i] = kotlin.runCatching {
FileXImageSource(FileX(this@ReaderActivity, image))
- }.onFailure {
+ }.onFailure {
+ logger.warning(it)
model.error(i)
}.getOrNull()
}
@@ -116,7 +118,7 @@ class ReaderActivity : ComponentActivity(), DIAware {
show(WindowInsetsCompat.Type.systemBars())
}
- AppCompatTheme {
+ PupilTheme {
Scaffold(
topBar = {
if (!isFullscreen)
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/composable/FloatingSearchBar.kt b/app/src/main/java/xyz/quaver/pupil/ui/composable/FloatingSearchBar.kt
new file mode 100644
index 00000000..6b6e9cc5
--- /dev/null
+++ b/app/src/main/java/xyz/quaver/pupil/ui/composable/FloatingSearchBar.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.ui.composable
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.text.BasicTextField
+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.Menu
+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.graphics.SolidColor
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import xyz.quaver.pupil.R
+
+@Preview
+@Composable
+fun FloatingSearchBar(
+ modifier: Modifier = Modifier,
+ query: String = "",
+ onQueryChange: (String) -> Unit = { },
+ navigationIcon: @Composable () -> Unit = {
+ Icon(
+ Icons.Default.Menu,
+ modifier = Modifier.size(24.dp),
+ contentDescription = null,
+ tint = MaterialTheme.colors.onSurface.copy(alpha = 0.5f)
+ )
+ },
+ actions: @Composable RowScope.() -> Unit = { }
+) {
+ Card(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(64.dp)
+ .padding(8.dp, 8.dp)
+ .background(Color.Transparent),
+ elevation = 8.dp
+ ) {
+ Row(
+ modifier = Modifier.fillMaxSize().padding(16.dp, 0.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ navigationIcon()
+
+ BasicTextField(
+ modifier = Modifier.weight(1f).padding(16.dp, 0.dp),
+ value = query,
+ onValueChange = onQueryChange,
+ singleLine = true,
+ cursorBrush = SolidColor(MaterialTheme.colors.primary),
+ decorationBox = { innerTextField ->
+ if (query.isEmpty())
+ Text(
+ stringResource(R.string.search_hint),
+ color = MaterialTheme.colors.onSurface.copy(alpha = 0.5f)
+ )
+
+ innerTextField()
+ }
+ )
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(24.dp),
+ content = actions
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/composable/MultipleFloatingActionButton.kt b/app/src/main/java/xyz/quaver/pupil/ui/composable/MultipleFloatingActionButton.kt
index 638fee8f..761f6edf 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/composable/MultipleFloatingActionButton.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/composable/MultipleFloatingActionButton.kt
@@ -20,6 +20,8 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@@ -41,7 +43,7 @@ enum class FloatingActionButtonState(private val isExpanded: Boolean) {
}
data class SubFabItem(
- val icon: ImageVector,
+ val icon: Any, // ImageVector | Painter | ImageBitmap
val label: String? = null,
val onClick: ((SubFabItem) -> Unit)? = null
)
@@ -84,7 +86,15 @@ fun MiniFloatingActionButton(
elevation = elevation,
interactionSource = interactionSource
) {
- Icon(item.icon, contentDescription = null)
+ when (item.icon) {
+ is ImageVector ->
+ Icon(item.icon, contentDescription = null)
+ is Painter ->
+ Icon(item.icon, contentDescription = null)
+ is ImageBitmap ->
+ Icon(item.icon, contentDescription = null)
+ else -> error("Icon is not ImageVector | Painter | ImageBitmap")
+ }
}
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/theme/Color.kt b/app/src/main/java/xyz/quaver/pupil/ui/theme/Color.kt
new file mode 100644
index 00000000..63746bc6
--- /dev/null
+++ b/app/src/main/java/xyz/quaver/pupil/ui/theme/Color.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val LightBlue300 = Color(0xFF4FC3F7)
+val LightBlue700 = Color(0xFF0288D1)
+val Pink600 = Color(0xFFD81B60)
+val Blue700 = Color(0xFF1976D2)
+val GreenA700 = Color(0xFF00C853)
+val Orange500 = Color(0xFFFF9800)
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/theme/Shape.kt b/app/src/main/java/xyz/quaver/pupil/ui/theme/Shape.kt
new file mode 100644
index 00000000..074ade75
--- /dev/null
+++ b/app/src/main/java/xyz/quaver/pupil/ui/theme/Shape.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.ui.theme
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Shapes
+import androidx.compose.ui.unit.dp
+
+val Shapes = Shapes(
+ small = RoundedCornerShape(4.dp),
+ medium = RoundedCornerShape(4.dp),
+ large = RoundedCornerShape(0.dp)
+)
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/theme/Theme.kt b/app/src/main/java/xyz/quaver/pupil/ui/theme/Theme.kt
new file mode 100644
index 00000000..bc238c6d
--- /dev/null
+++ b/app/src/main/java/xyz/quaver/pupil/ui/theme/Theme.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.darkColors
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+
+private val DarkColorPalette = darkColors()
+private val LightColorPalette = lightColors()
+
+@Composable
+fun PupilTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable () -> Unit
+) {
+ val colors = if (darkTheme) DarkColorPalette else LightColorPalette
+
+ MaterialTheme(
+ colors = colors,
+ typography = Typography,
+ shapes = Shapes,
+ content = content
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/theme/Type.kt b/app/src/main/java/xyz/quaver/pupil/ui/theme/Type.kt
new file mode 100644
index 00000000..6229e003
--- /dev/null
+++ b/app/src/main/java/xyz/quaver/pupil/ui/theme/Type.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.ui.theme
+
+import androidx.compose.material.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+val Typography = Typography(
+ body1 = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp
+ )
+)
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCardView.kt b/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCardView.kt
index 8d997e50..881eed3f 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCardView.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCardView.kt
@@ -26,18 +26,16 @@ import xyz.quaver.pupil.sources.Source
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ProgressCardView(progress: Float? = null, onLongClick: (() -> Unit)? = null, onClick: () -> Unit, content: @Composable () -> Unit) {
- MaterialTheme(if (isSystemInDarkTheme()) darkColors() else lightColors()) {
- Card(
- modifier = Modifier
- .padding(8.dp)
- .combinedClickable(onClick = onClick, onLongClick = onLongClick),
- shape = RoundedCornerShape(4.dp),
- elevation = 4.dp
- ) {
- Column {
- progress?.run { LinearProgressIndicator(progress = progress, modifier = Modifier.fillMaxWidth()) }
- content.invoke()
- }
+ Card(
+ modifier = Modifier
+ .padding(8.dp)
+ .combinedClickable(onClick = onClick, onLongClick = onLongClick),
+ shape = RoundedCornerShape(4.dp),
+ elevation = 4.dp
+ ) {
+ Column {
+ progress?.run { LinearProgressIndicator(progress = progress, modifier = Modifier.fillMaxWidth()) }
+ content()
}
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt b/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt
index 5d14690b..e460d0ba 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt
@@ -20,6 +20,7 @@ package xyz.quaver.pupil.ui.viewmodel
import android.annotation.SuppressLint
import android.app.Application
+import androidx.compose.runtime.mutableStateListOf
import androidx.lifecycle.*
import kotlinx.coroutines.*
import org.kodein.di.DIAware
@@ -38,8 +39,7 @@ import kotlin.random.Random
class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
override val di by closestDI()
- private val _searchResults = MutableLiveData>()
- val searchResults = _searchResults as LiveData>
+ val searchResults = mutableStateListOf()
private val _loading = MutableLiveData(false)
val loading = _loading as LiveData
@@ -127,29 +127,28 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
queryJob?.cancel()
_loading.value = true
- val results = mutableListOf()
- _searchResults.value = results
queryJob = viewModelScope.launch {
- val channel = withContext(Dispatchers.IO) {
- val (channel, count) = source.search(
- query.value ?: "",
- (currentPage - 1) * perPage until currentPage * perPage,
- sortModeIndex
- )
+ launch(Dispatchers.Default) {
+ val channel = withContext(Dispatchers.IO) {
+ val (channel, count) = source.search(
+ query.value ?: "",
+ (currentPage - 1) * perPage until currentPage * perPage,
+ sortModeIndex
+ )
- totalItems.postValue(count)
+ totalItems.postValue(count)
- channel
+ channel
+ }
+
+ for (result in channel) {
+ yield()
+ searchResults.add(result)
+ }
+
+ _loading.postValue(false)
}
-
- for (result in channel) {
- yield()
- results.add(result)
- _searchResults.value = results.toList()
- }
-
- _loading.value = false
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/ReaderViewModel.kt b/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/ReaderViewModel.kt
index 237bf538..676f40b4 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/ReaderViewModel.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/ReaderViewModel.kt
@@ -144,6 +144,9 @@ class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware {
progressList.addAll(List(imageCount) { 0f })
imageList.addAll(List(imageCount) { null })
+ totalProgressMutex.withLock {
+ totalProgress = 0
+ }
images.forEachIndexed { index, image ->
when (val scheme = image.takeWhile { it != ':' }) {
@@ -169,7 +172,7 @@ class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware {
totalProgress++
}
} else {
- TODO("Handle error")
+ error(index)
}
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/util/ImageCache.kt b/app/src/main/java/xyz/quaver/pupil/util/ImageCache.kt
index dbb659ba..37f7df85 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/ImageCache.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/ImageCache.kt
@@ -34,6 +34,8 @@ import kotlinx.coroutines.channels.Channel
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.hitomi.sha256
import java.io.File
import java.util.concurrent.ConcurrentHashMap
@@ -54,6 +56,8 @@ class NetworkCache(context: Context) : DIAware {
private val networkScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher())
+ private val logger = newLogger(LoggerFactory.default)
+
@OptIn(ExperimentalCoroutinesApi::class)
suspend fun load(requestBuilder: HttpRequestBuilder.() -> Unit): File = coroutineScope {
val request = HttpRequestBuilder().apply(requestBuilder)
@@ -95,6 +99,7 @@ class NetworkCache(context: Context) : DIAware {
progressChannel.close()
}
}.onFailure {
+ logger.warning(it)
file.delete()
FirebaseCrashlytics.getInstance().recordException(it)
progressChannel.close(it)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c13ab423..e1094792 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,5 +1,5 @@
- Pupil
+ Pupil-BETA
https://api.github.com/repos/tom5079/Pupil/releases
diff --git a/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt b/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt
index 95278d0d..652d776a 100644
--- a/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt
+++ b/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt
@@ -31,6 +31,8 @@ import kotlinx.coroutines.runBlocking
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import org.junit.Test
+import xyz.quaver.hitomi.getGalleryInfo
+import xyz.quaver.hitomi.imageUrlFromImage
import java.lang.reflect.ParameterizedType
import kotlin.reflect.KClass
import kotlin.reflect.KType
@@ -40,8 +42,11 @@ class ExampleUnitTest {
@Test
fun test() {
- runBlocking {
+ val galleryID = 479010
+ val files = getGalleryInfo(galleryID).files
+ files.forEachIndexed { i, it ->
+ println("$i: ${imageUrlFromImage(galleryID, it, true)}")
}
}
diff --git a/build.gradle.kts b/build.gradle.kts
index 803eefd3..a8bcab60 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,10 +6,10 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath("com.android.tools.build:gradle:7.0.3")
- classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10")
- classpath("org.jetbrains.kotlin:kotlin-android-extensions:1.5.21")
- classpath("org.jetbrains.kotlin:kotlin-serialization:1.5.21")
+ 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("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