SourceSelectDialog

This commit is contained in:
tom5079
2021-12-16 16:38:59 +09:00
parent 78ba11ca5f
commit 077d9b976c
10 changed files with 137 additions and 257 deletions

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<runningDeviceTargetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_3a_API_30_x86.avd" />
<type value="SERIAL_NUMBER" />
<value value="ce021712e3b19b2b04" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2021-12-16T03:12:12.593009Z" />
</runningDeviceTargetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2021-12-16T03:48:05.512086Z" />
</component>
</project>

3
.idea/misc.xml generated
View File

@@ -28,6 +28,8 @@
<entry key="../../../../layout/compose-model-1639478149655.xml" value="0.33" />
<entry key="../../../../layout/compose-model-1639535152524.xml" value="0.3055555555555556" />
<entry key="../../../../layout/compose-model-1639538998660.xml" value="0.30277777777777776" />
<entry key="../../../../layout/compose-model-1639625734547.xml" value="0.1" />
<entry key="../../../../layout/compose-model-1639629588722.xml" value="0.3472222222222222" />
<entry key="../../../../layout/custom_preview.xml" value="0.518974358974359" />
<entry key="app/src/main/res/drawable/avd_star.xml" value="0.2722222222222222" />
<entry key="app/src/main/res/drawable/history.xml" value="0.3055555555555556" />
@@ -38,6 +40,7 @@
<entry key="app/src/main/res/layout/reader_activity.xml" value="0.3" />
<entry key="app/src/main/res/layout/reader_item.xml" value="0.1" />
<entry key="app/src/main/res/layout/search_result_item.xml" value="0.2489868287740628" />
<entry key="app/src/main/res/layout/source_select_dialog_item.xml" value="0.5119791666666667" />
</map>
</option>
</component>

View File

@@ -60,7 +60,7 @@ abstract class Source {
abstract suspend fun info(itemID: String): ItemInfo
@Composable
open fun SearchResult(itemInfo: ItemInfo, onEvent: ((SearchResultEvent) -> Unit)? = null) { }
open fun SearchResult(itemInfo: ItemInfo, onEvent: (SearchResultEvent) -> Unit = { }) { }
open fun getHeadersBuilderForImage(itemID: String, url: String): HeadersBuilder.() -> Unit = { }

View File

@@ -73,7 +73,7 @@ class History(override val di: DI) : Source(), DIAware {
@Composable
override fun SearchResult(itemInfo: ItemInfo, onEvent: ((SearchResultEvent) -> Unit)?) {
override fun SearchResult(itemInfo: ItemInfo, onEvent: (SearchResultEvent) -> Unit) {
}

View File

@@ -120,7 +120,7 @@ data class HitomiItemInfo(
class Hitomi(app: Application) : Source(), DIAware {
override val di: DI by closestDI(app)
override val di by closestDI(app)
private val logger = newLogger(LoggerFactory.default)
@@ -223,10 +223,10 @@ class Hitomi(app: Application) : Source(), DIAware {
}
@Composable
override fun SearchResult(itemInfo: ItemInfo, onEvent: ((SearchResultEvent) -> Unit)?) {
override fun SearchResult(itemInfo: ItemInfo, onEvent: (SearchResultEvent) -> Unit) {
itemInfo as HitomiItemInfo
FullSearchResult(itemInfo = itemInfo)
FullSearchResult(itemInfo = itemInfo, onEvent = onEvent)
}
override fun getHeadersBuilderForImage(itemID: String, url: String): HeadersBuilder.() -> Unit = {
@@ -434,7 +434,7 @@ class Hitomi(app: Application) : Source(), DIAware {
@OptIn(ExperimentalCoilApi::class)
@Composable
fun FullSearchResult(itemInfo: HitomiItemInfo) {
fun FullSearchResult(itemInfo: HitomiItemInfo, onEvent: (SearchResultEvent) -> Unit) {
var group by remember { mutableStateOf(emptyList<String>()) }
var pageCount by remember { mutableStateOf("-") }

View File

@@ -1,83 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.sources
import android.app.Application
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.instance
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.pupil.R
/*
class ImHentai(override val di: DI) : Source(), DIAware {
private val app: Application by instance()
private val client: HttpClient by instance()
override val name: String
get() = ImHentai.name
override val iconResID: Int
get() = R.drawable.ic_imhentai
override val preferenceID: Int
get() = R.xml.imhentai_preferences
override val availableSortMode = app.resources.getStringArray(R.array.imhentai_sort_mode).toList()
override suspend fun search(query: String, range: IntRange, sortMode: Int): Pair<Channel<ItemInfo>, Int> = withContext(Dispatchers.IO) {
val channel = Channel<ItemInfo>()
val doc = Jsoup.connect("https://imhentai.xxx/search/?key=$query").get()
val count = countRegex.find(doc.getElementsByClass("heading2").text())?.groupValues?.get(1)?.toIntOrNull() ?: 0
launch {
doc.getElementsByClass("thumb")
}
return@withContext Pair(channel, count)
}
override suspend fun suggestion(query: String): List<SearchSuggestion> {
TODO("Not yet implemented")
}
override suspend fun images(itemID: String): List<String> {
TODO("Not yet implemented")
}
override suspend fun info(itemID: String): ItemInfo {
TODO("Not yet implemented")
}
companion object {
private const val name = "imhentai"
private val countRegex = Regex("""\(\d+\) results found.""")
private val idRegex = Regex("""/gallery/(\d+)/""")
private fun transform(item: Element) {
val caption = item.select(".caption a")
}
}
}*/

View File

@@ -24,59 +24,48 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.MutatePriority
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.*
import androidx.compose.foundation.interaction.MutableInteractionSource
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.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
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.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastAny
import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.WindowCompat
import com.google.accompanist.drawablepainter.rememberDrawablePainter
import com.google.accompanist.systemuicontroller.rememberSystemUiController
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 org.kodein.di.compose.withDI
import org.kodein.log.LoggerFactory
import org.kodein.log.newLogger
import xyz.quaver.pupil.*
import xyz.quaver.pupil.R
import xyz.quaver.pupil.sources.Source
import xyz.quaver.pupil.sources.SearchResultEvent
import xyz.quaver.pupil.types.*
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.dialog.SourceSelectDialog
import xyz.quaver.pupil.ui.dialog.SourceSelectDialogItem
import xyz.quaver.pupil.ui.theme.PupilTheme
import xyz.quaver.pupil.ui.view.ProgressCardView
import xyz.quaver.pupil.ui.viewmodel.MainViewModel
@@ -101,7 +90,7 @@ class MainActivity : ComponentActivity(), DIAware {
setContent {
PupilTheme {
val source: Source? by model.source.observeAsState(null)
val focusManager = LocalFocusManager.current
var isFabExpanded by remember { mutableStateOf(FloatingActionButtonState.COLLAPSED) }
var isFabVisible by remember { mutableStateOf(true) }
@@ -121,10 +110,17 @@ class MainActivity : ComponentActivity(), DIAware {
}
}
var openSourceSelectDialog by remember { mutableStateOf(false) }
LaunchedEffect(navigationIconProgress) {
navigationIcon.progress = navigationIconProgress
}
if (openSourceSelectDialog)
SourceSelectDialog {
openSourceSelectDialog = false
}
Scaffold(
floatingActionButton = {
MultipleFloatingActionButton(
@@ -178,19 +174,23 @@ class MainActivity : ComponentActivity(), DIAware {
) {
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)
})
}
progress = 0.5f
) {
source?.SearchResult(itemInfo = itemInfo)
model.source.SearchResult(itemInfo = itemInfo) { event ->
when (event.type) {
SearchResultEvent.Type.OPEN_READER -> {
startActivity(
Intent(
this@MainActivity,
ReaderActivity::class.java
).apply {
putExtra("source", model.source.name)
putExtra("id", itemInfo.itemID)
})
}
else -> TODO("")
}
}
}
}
}
@@ -206,10 +206,24 @@ class MainActivity : ComponentActivity(), DIAware {
Icon(
painter = rememberDrawablePainter(navigationIcon),
contentDescription = null,
modifier = Modifier.size(24.dp)
modifier = Modifier
.size(24.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false)
) {
focusManager.clearFocus()
}
)
},
actions = {
Image(
painterResource(model.source.iconResID),
contentDescription = null,
modifier = Modifier.size(24.dp).clickable {
openSourceSelectDialog = true
}
)
Icon(
Icons.Default.Sort,
contentDescription = null,

View File

@@ -18,37 +18,67 @@
package xyz.quaver.pupil.ui.dialog
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import org.kodein.di.compose.rememberInstance
import xyz.quaver.pupil.sources.Source
import xyz.quaver.pupil.sources.SourceEntries
class SourceSelectDialog : DialogFragment(), DIAware {
@Composable
fun SourceSelectDialogItem(source: Source) {
Row(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Image(
painter = painterResource(source.iconResID),
contentDescription = null,
modifier = Modifier.size(24.dp)
)
override val di by closestDI()
Text(
source.name,
modifier = Modifier.weight(1f)
)
var onSourceSelectedListener: ((String) -> Unit)? = null
var onSourceSettingsSelectedListener: ((String) -> Unit)? = null
Icon(
Icons.Default.Settings,
contentDescription = null,
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.5f)
)
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {/*
return Dialog(requireContext()).apply {
window?.requestFeature(Window.FEATURE_NO_TITLE)
window?.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
val sourcesWithPreferenceID = direct.instance<SourcePreferenceIDs>().map { it.first }
val preferences = direct.instance<SourceEntries>().filter {
it.first in sourcesWithPreferenceID
}.toSet()
setContentView(RecyclerView(context).apply {
layoutManager = LinearLayoutManager(context)
adapter = SourceAdapter(preferences).apply {
onSourceSelectedListener = this@SourceSelectDialog.onSourceSelectedListener
onSourceSettingsSelectedListener = this@SourceSelectDialog.onSourceSettingsSelectedListener
}
})
Button(onClick = { /*TODO*/ }) {
Text("GO")
}
*/return super.onCreateDialog(savedInstanceState)}
}
}
@Preview
@Composable
fun SourceSelectDialog(onDismissRequest: () -> Unit = { }) {
val sourceEntries: SourceEntries by rememberInstance()
Dialog(onDismissRequest = onDismissRequest) {
Card(
elevation = 8.dp,
shape = RoundedCornerShape(12.dp)
) {
Column() {
sourceEntries.forEach { SourceSelectDialogItem(it.second) }
}
}
}
}

View File

@@ -25,11 +25,9 @@ import xyz.quaver.pupil.sources.Source
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ProgressCardView(progress: Float? = null, onLongClick: (() -> Unit)? = null, onClick: () -> Unit, content: @Composable () -> Unit) {
fun ProgressCardView(progress: Float? = null, content: @Composable () -> Unit) {
Card(
modifier = Modifier
.padding(8.dp)
.combinedClickable(onClick = onClick, onLongClick = onLongClick),
modifier = Modifier.padding(8.dp),
shape = RoundedCornerShape(4.dp),
elevation = 4.dp
) {
@@ -38,64 +36,4 @@ fun ProgressCardView(progress: Float? = null, onLongClick: (() -> Unit)? = null,
content()
}
}
}
class ProgressCardView @JvmOverloads constructor(context: Context, attr: AttributeSet? = null, defStyle: Int = R.attr.cardViewStyle) : CardView(context, attr, defStyle) {
enum class Type {
LOADING,
CACHE,
DOWNLOAD
}
var type: Type = Type.LOADING
set(value) {
field = value
when (field) {
Type.LOADING -> R.color.colorAccent
Type.CACHE -> R.color.material_blue_700
Type.DOWNLOAD -> R.color.material_green_a700
}.let {
val color = ContextCompat.getColor(context, it)
DrawableCompat.setTint(binding.progressbar.progressDrawable, color)
}
}
var progress: Int
get() = binding.progressbar.progress
set(value) {
binding.progressbar.progress = value
}
var max: Int
get() = binding.progressbar.max
set(value) {
binding.progressbar.max = value
binding.progressbar.visibility =
if (value == 0)
GONE
else
VISIBLE
}
val binding = ProgressCardViewBinding.inflate(LayoutInflater.from(context), this)
init {
binding.content.setOnClickListener {
performClick()
}
binding.content.setOnLongClickListener {
performLongClick()
}
}
override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
if (childCount == 0)
super.addView(child, index, params)
else
binding.content.addView(child, index, params)
}
}

View File

@@ -20,10 +20,7 @@ package xyz.quaver.pupil.ui.viewmodel
import android.annotation.SuppressLint
import android.app.Application
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.*
import androidx.lifecycle.*
import kotlinx.coroutines.*
import org.kodein.di.DIAware
@@ -34,6 +31,7 @@ import org.kodein.log.LoggerFactory
import org.kodein.log.newLogger
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.pupil.sources.*
import xyz.quaver.pupil.sources.hitomi.Hitomi
import xyz.quaver.pupil.util.Preferences
import xyz.quaver.pupil.util.source
import kotlin.math.ceil
@@ -61,21 +59,13 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
direct.source(it)
}
private var sourceFactory: (String) -> Source = defaultSourceFactory
private val _source = MutableLiveData<Source>()
val source: LiveData<Source> = _source
var source by mutableStateOf(sourceFactory("hitomi.la"))
private set
val availableSortMode = Transformations.map(_source) {
it.availableSortMode
}
var sortModeIndex by mutableStateOf(0)
private set
val sortModeIndex = MutableLiveData<Int>()
val sourceIcon = Transformations.map(_source) {
it.iconResID
}
private val _currentPage = MutableLiveData<Int>()
val currentPage: LiveData<Int> = _currentPage
var currentPage by mutableStateOf(1)
private val totalItems = MutableLiveData<Int>()
@@ -88,14 +78,9 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
private val _suggestions = MutableLiveData<List<SearchSuggestion>>()
val suggestions: LiveData<List<SearchSuggestion>> = _suggestions
init {
setSourceAndReset("hitomi.la")
}
fun setSourceAndReset(sourceName: String) {
_source.value = sourceFactory(sourceName).also {
sortModeIndex.value = 0
}
source = sourceFactory(sourceName)
sortModeIndex = 0
query = ""
resetAndQuery()
@@ -103,7 +88,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
fun resetAndQuery() {
queryStack.add(query)
setPage(1)
currentPage = 1
query()
}
@@ -119,16 +104,13 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
when {
mode == MainMode.DOWNLOADS -> "downloads"
//source.value is Downloads -> "hitomi.la"
else -> source.value!!.name
else -> source.name
}
)
}
fun query() {
val perPage = Preferences["per_page", "25"].toInt()
val source = _source.value ?: error("Source is null")
val sortModeIndex = sortModeIndex.value ?: 0
val currentPage = currentPage.value ?: 1
suggestionJob?.cancel()
queryJob?.cancel()
@@ -156,10 +138,6 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
}
}
fun prevPage() { _currentPage.value = _currentPage.value!! - 1 }
fun nextPage() { _currentPage.value = _currentPage.value!! + 1 }
fun setPage(page: Int) { _currentPage.value = page }
fun random(callback: (ItemInfo) -> Unit) {
if (totalItems.value!! == 0)
return
@@ -168,12 +146,12 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
viewModelScope.launch {
withContext(Dispatchers.IO) {
_source.value?.search(
source.search(
query + Preferences["default_query", ""],
random .. random,
sortModeIndex.value!!
)?.first?.receive()
}?.let(callback)
sortModeIndex
).first.receive()
}.let(callback)
}
}
@@ -186,7 +164,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
@SuppressLint("NullSafeMutableLiveData")
_suggestions.value = withContext(Dispatchers.IO) {
kotlin.runCatching {
_source.value!!.suggestion(query)
source.suggestion(query)
}.getOrElse { emptyList() }
}
}