Update ktlint to 1.3.0
This commit is contained in:
1 parent
3946428df6
commit
5a3c165ff1
79 files changed
+707
-979
No files matched your search
@@ -294,13 +294,15 @@ aboutLibraries {
|
||||
duplicationRule = GROUP
|
||||
}
|
||||
|
||||
val ktlintVersion = libs.ktlint.get().version
|
||||
|
||||
spotless {
|
||||
kotlin {
|
||||
// https://github.com/diffplug/spotless/issues/111
|
||||
target("src/**/*.kt")
|
||||
ktlint()
|
||||
ktlint(ktlintVersion)
|
||||
}
|
||||
kotlinGradle {
|
||||
ktlint()
|
||||
ktlint(ktlintVersion)
|
||||
}
|
||||
}
|
||||
+5
-9
@@ -48,11 +48,8 @@ fun <T> Modifier.anchoredHorizontalDraggable(
|
||||
): Modifier = this then AnchoredHorizontalDraggableElement(
|
||||
state = state,
|
||||
canDrag = { (x, y) ->
|
||||
if (x > 0) {
|
||||
enableDragFromStartToEnd
|
||||
} else {
|
||||
enableDragFromEndToStart
|
||||
} && x.absoluteValue > y.absoluteValue * 2f
|
||||
val enableDrag = if (x > 0) enableDragFromStartToEnd else enableDragFromEndToStart
|
||||
enableDrag && x.absoluteValue > y.absoluteValue * 2f
|
||||
},
|
||||
enabled = enabled,
|
||||
startDragImmediately = startDragImmediately,
|
||||
@@ -124,9 +121,7 @@ private class AnchoredHorizontalDraggableNode<T>(
|
||||
private val isReverseDirection: Boolean
|
||||
get() = requireLayoutDirection() == LayoutDirection.Rtl
|
||||
|
||||
override fun canDrag(change: PointerInputChange): Boolean {
|
||||
return canDrag(change.positionChange().reverseIfNeeded())
|
||||
}
|
||||
override fun canDrag(change: PointerInputChange): Boolean = canDrag(change.positionChange().reverseIfNeeded())
|
||||
|
||||
override suspend fun drag(forEachDelta: suspend ((dragDelta: DragDelta) -> Unit) -> Unit) {
|
||||
state.anchoredDrag {
|
||||
@@ -240,7 +235,8 @@ private class DraggableAnchorsNode<T>(
|
||||
var state: AnchoredDraggableState<T>,
|
||||
var anchors: (size: IntSize, constraints: Constraints) -> Pair<DraggableAnchors<T>, T>,
|
||||
var orientation: Orientation,
|
||||
) : Modifier.Node(), LayoutModifierNode {
|
||||
) : Modifier.Node(),
|
||||
LayoutModifierNode {
|
||||
private var didLookahead: Boolean = false
|
||||
|
||||
override fun onDetach() {
|
||||
|
||||
@@ -46,7 +46,9 @@ import kotlinx.coroutines.launch
|
||||
*/
|
||||
internal abstract class HorizontalDragGestureNode(
|
||||
enabled: Boolean,
|
||||
) : DelegatingNode(), PointerInputModifierNode, CompositionLocalConsumerModifierNode {
|
||||
) : DelegatingNode(),
|
||||
PointerInputModifierNode,
|
||||
CompositionLocalConsumerModifierNode {
|
||||
|
||||
protected var enabled = enabled
|
||||
private set
|
||||
@@ -132,65 +134,63 @@ internal abstract class HorizontalDragGestureNode(
|
||||
pointerInputNode?.onPointerEvent(pointerEvent, pass, bounds)
|
||||
}
|
||||
|
||||
private fun initializePointerInputNode(): SuspendingPointerInputModifierNode {
|
||||
return SuspendingPointerInputModifierNode {
|
||||
// re-create tracker when pointer input block restarts. This lazily creates the tracker
|
||||
// only when it is need.
|
||||
val velocityTracker = VelocityTracker()
|
||||
val onDragStart: (change: PointerInputChange, initialDelta: Offset) -> Unit =
|
||||
{ startEvent, initialDelta ->
|
||||
if (!isListeningForEvents) {
|
||||
if (channel == null) {
|
||||
channel = Channel(capacity = Channel.UNLIMITED)
|
||||
}
|
||||
startListeningForEvents()
|
||||
private fun initializePointerInputNode(): SuspendingPointerInputModifierNode = SuspendingPointerInputModifierNode {
|
||||
// re-create tracker when pointer input block restarts. This lazily creates the tracker
|
||||
// only when it is need.
|
||||
val velocityTracker = VelocityTracker()
|
||||
val onDragStart: (change: PointerInputChange, initialDelta: Offset) -> Unit =
|
||||
{ startEvent, initialDelta ->
|
||||
if (!isListeningForEvents) {
|
||||
if (channel == null) {
|
||||
channel = Channel(capacity = Channel.UNLIMITED)
|
||||
}
|
||||
val xSign = sign(startEvent.position.x)
|
||||
val ySign = sign(startEvent.position.y)
|
||||
val adjustedStart = startEvent.position -
|
||||
Offset(initialDelta.x * xSign, initialDelta.y * ySign)
|
||||
|
||||
channel?.trySend(DragStarted(adjustedStart))
|
||||
startListeningForEvents()
|
||||
}
|
||||
val xSign = sign(startEvent.position.x)
|
||||
val ySign = sign(startEvent.position.y)
|
||||
val adjustedStart = startEvent.position -
|
||||
Offset(initialDelta.x * xSign, initialDelta.y * ySign)
|
||||
|
||||
val onDragEnd: () -> Unit = {
|
||||
val maximumVelocity = currentValueOf(LocalViewConfiguration)
|
||||
.maximumFlingVelocity
|
||||
val velocity = velocityTracker.calculateVelocity(
|
||||
Velocity(maximumVelocity, maximumVelocity),
|
||||
channel?.trySend(DragStarted(adjustedStart))
|
||||
}
|
||||
|
||||
val onDragEnd: () -> Unit = {
|
||||
val maximumVelocity = currentValueOf(LocalViewConfiguration)
|
||||
.maximumFlingVelocity
|
||||
val velocity = velocityTracker.calculateVelocity(
|
||||
Velocity(maximumVelocity, maximumVelocity),
|
||||
)
|
||||
velocityTracker.resetTracking()
|
||||
channel?.trySend(DragStopped(velocity))
|
||||
}
|
||||
|
||||
val onDragCancel: () -> Unit = {
|
||||
channel?.trySend(DragCancelled)
|
||||
}
|
||||
|
||||
val shouldAwaitTouchSlop: () -> Boolean = {
|
||||
!startDragImmediately()
|
||||
}
|
||||
|
||||
val onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit =
|
||||
{ change, delta ->
|
||||
velocityTracker.addPointerInputChange(change)
|
||||
channel?.trySend(DragDelta(delta))
|
||||
}
|
||||
|
||||
coroutineScope {
|
||||
try {
|
||||
detectHorizontalDragGestures(
|
||||
canDrag = ::canDrag,
|
||||
onDragStart = onDragStart,
|
||||
onDragEnd = onDragEnd,
|
||||
onDragCancel = onDragCancel,
|
||||
shouldAwaitTouchSlop = shouldAwaitTouchSlop,
|
||||
onDrag = onDrag,
|
||||
)
|
||||
velocityTracker.resetTracking()
|
||||
channel?.trySend(DragStopped(velocity))
|
||||
}
|
||||
|
||||
val onDragCancel: () -> Unit = {
|
||||
} catch (cancellation: CancellationException) {
|
||||
channel?.trySend(DragCancelled)
|
||||
}
|
||||
|
||||
val shouldAwaitTouchSlop: () -> Boolean = {
|
||||
!startDragImmediately()
|
||||
}
|
||||
|
||||
val onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit =
|
||||
{ change, delta ->
|
||||
velocityTracker.addPointerInputChange(change)
|
||||
channel?.trySend(DragDelta(delta))
|
||||
}
|
||||
|
||||
coroutineScope {
|
||||
try {
|
||||
detectHorizontalDragGestures(
|
||||
canDrag = ::canDrag,
|
||||
onDragStart = onDragStart,
|
||||
onDragEnd = onDragEnd,
|
||||
onDragCancel = onDragCancel,
|
||||
shouldAwaitTouchSlop = shouldAwaitTouchSlop,
|
||||
onDrag = onDrag,
|
||||
)
|
||||
} catch (cancellation: CancellationException) {
|
||||
channel?.trySend(DragCancelled)
|
||||
if (!isActive) throw cancellation
|
||||
}
|
||||
if (!isActive) throw cancellation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@file:Suppress("ktlint:standard:property-naming", "ktlint:standard:function-naming")
|
||||
@file:Suppress("ktlint:standard:property-naming")
|
||||
|
||||
package androidx.compose.material3
|
||||
|
||||
@@ -230,10 +230,8 @@ class DrawerState2(
|
||||
fun rememberDrawerState2(
|
||||
initialValue: DrawerValue,
|
||||
confirmStateChange: (DrawerValue) -> Boolean = { true },
|
||||
): DrawerState2 {
|
||||
return rememberSaveable(saver = DrawerState2.Saver(confirmStateChange)) {
|
||||
DrawerState2(initialValue, confirmStateChange)
|
||||
}
|
||||
): DrawerState2 = rememberSaveable(saver = DrawerState2.Saver(confirmStateChange)) {
|
||||
DrawerState2(initialValue, confirmStateChange)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
+14
-36
@@ -912,16 +912,12 @@ open class SubsamplingScaleImageView @JvmOverloads constructor(
|
||||
/**
|
||||
* Convert screen to source x coordinate.
|
||||
*/
|
||||
private fun viewToSourceX(vx: Float): Float {
|
||||
return if (vTranslate == null) Float.NaN else (vx - vTranslate!!.x) / scale
|
||||
}
|
||||
private fun viewToSourceX(vx: Float): Float = if (vTranslate == null) Float.NaN else (vx - vTranslate!!.x) / scale
|
||||
|
||||
/**
|
||||
* Convert screen to source y coordinate.
|
||||
*/
|
||||
private fun viewToSourceY(vy: Float): Float {
|
||||
return if (vTranslate == null) Float.NaN else (vy - vTranslate!!.y) / scale
|
||||
}
|
||||
private fun viewToSourceY(vy: Float): Float = if (vTranslate == null) Float.NaN else (vy - vTranslate!!.y) / scale
|
||||
|
||||
/**
|
||||
* Convert screen coordinate to source coordinate.
|
||||
@@ -929,9 +925,7 @@ open class SubsamplingScaleImageView @JvmOverloads constructor(
|
||||
* @param vxy view X/Y coordinate.
|
||||
* @return a coordinate representing the corresponding source coordinate.
|
||||
*/
|
||||
fun viewToSourceCoord(vxy: PointF): PointF? {
|
||||
return viewToSourceCoord(vxy.x, vxy.y, PointF())
|
||||
}
|
||||
fun viewToSourceCoord(vxy: PointF): PointF? = viewToSourceCoord(vxy.x, vxy.y, PointF())
|
||||
|
||||
/**
|
||||
* Convert screen coordinate to source coordinate.
|
||||
@@ -952,16 +946,12 @@ open class SubsamplingScaleImageView @JvmOverloads constructor(
|
||||
/**
|
||||
* Convert source to view x coordinate.
|
||||
*/
|
||||
private fun sourceToViewX(sx: Float): Float {
|
||||
return if (vTranslate == null) Float.NaN else sx * scale + vTranslate!!.x
|
||||
}
|
||||
private fun sourceToViewX(sx: Float): Float = if (vTranslate == null) Float.NaN else sx * scale + vTranslate!!.x
|
||||
|
||||
/**
|
||||
* Convert source to view y coordinate.
|
||||
*/
|
||||
private fun sourceToViewY(sy: Float): Float {
|
||||
return if (vTranslate == null) Float.NaN else sy * scale + vTranslate!!.y
|
||||
}
|
||||
private fun sourceToViewY(sy: Float): Float = if (vTranslate == null) Float.NaN else sy * scale + vTranslate!!.y
|
||||
|
||||
/**
|
||||
* Convert source coordinate to view coordinate.
|
||||
@@ -969,9 +959,7 @@ open class SubsamplingScaleImageView @JvmOverloads constructor(
|
||||
* @param sxy source coordinates to convert.
|
||||
* @return view coordinates.
|
||||
*/
|
||||
fun sourceToViewCoord(sxy: PointF?): PointF? {
|
||||
return sourceToViewCoord(sxy!!.x, sxy.y, PointF())
|
||||
}
|
||||
fun sourceToViewCoord(sxy: PointF?): PointF? = sourceToViewCoord(sxy!!.x, sxy.y, PointF())
|
||||
|
||||
/**
|
||||
* Convert source coordinate to view coordinate.
|
||||
@@ -1053,9 +1041,7 @@ open class SubsamplingScaleImageView @JvmOverloads constructor(
|
||||
/**
|
||||
* Adjust a requested scale to be within the allowed limits.
|
||||
*/
|
||||
private fun limitedScale(targetScale: Float): Float {
|
||||
return targetScale.coerceIn(minScale(), maxScale)
|
||||
}
|
||||
private fun limitedScale(targetScale: Float): Float = targetScale.coerceIn(minScale(), maxScale)
|
||||
|
||||
/**
|
||||
* Apply a selected type of easing.
|
||||
@@ -1067,12 +1053,10 @@ open class SubsamplingScaleImageView @JvmOverloads constructor(
|
||||
* @param duration Anm duration
|
||||
* @return Current value
|
||||
*/
|
||||
private fun ease(type: Int, time: Long, from: Float, change: Float, duration: Long): Float {
|
||||
return when (type) {
|
||||
EASE_IN_OUT_QUAD -> easeInOutQuad(time, from, change, duration)
|
||||
EASE_OUT_QUAD -> easeOutQuad(time, from, change, duration)
|
||||
else -> throw IllegalStateException("Unexpected easing type: $type")
|
||||
}
|
||||
private fun ease(type: Int, time: Long, from: Float, change: Float, duration: Long): Float = when (type) {
|
||||
EASE_IN_OUT_QUAD -> easeInOutQuad(time, from, change, duration)
|
||||
EASE_OUT_QUAD -> easeOutQuad(time, from, change, duration)
|
||||
else -> throw IllegalStateException("Unexpected easing type: $type")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1199,9 +1183,7 @@ open class SubsamplingScaleImageView @JvmOverloads constructor(
|
||||
*
|
||||
* @return the minimum scale as a source/view pixels ratio.
|
||||
*/
|
||||
fun getMinScale(): Float {
|
||||
return minScale()
|
||||
}
|
||||
fun getMinScale(): Float = minScale()
|
||||
|
||||
/**
|
||||
* Set the minimum scale allowed. A value of 1 means 1:1 pixels at minimum scale. You may wish to set this according
|
||||
@@ -1325,9 +1307,7 @@ open class SubsamplingScaleImageView @JvmOverloads constructor(
|
||||
* @param sCenter Target center point
|
||||
* @return [AnimationBuilder] instance. Call [SubsamplingScaleImageView.AnimationBuilder.start] to start the anim.
|
||||
*/
|
||||
fun animateCenter(sCenter: PointF): AnimationBuilder? {
|
||||
return if (isReady) AnimationBuilder(sCenter) else null
|
||||
}
|
||||
fun animateCenter(sCenter: PointF): AnimationBuilder? = if (isReady) AnimationBuilder(sCenter) else null
|
||||
|
||||
/**
|
||||
* Creates a scale animation builder, that when started will animate a zoom in or out. If this would move the image
|
||||
@@ -1337,9 +1317,7 @@ open class SubsamplingScaleImageView @JvmOverloads constructor(
|
||||
* @param sCenter Target source center.
|
||||
* @return [AnimationBuilder] instance. Call [SubsamplingScaleImageView.AnimationBuilder.start] to start the anim.
|
||||
*/
|
||||
fun animateScaleAndCenter(scale: Float, sCenter: PointF?): AnimationBuilder? {
|
||||
return if (isReady) AnimationBuilder(scale, sCenter) else null
|
||||
}
|
||||
fun animateScaleAndCenter(scale: Float, sCenter: PointF?): AnimationBuilder? = if (isReady) AnimationBuilder(scale, sCenter) else null
|
||||
|
||||
/**
|
||||
* An event listener, allowing subclasses and activities to be notified of significant events.
|
||||
|
||||
@@ -77,7 +77,9 @@ import splitties.init.appCtx
|
||||
private val lifecycle = ProcessLifecycleOwner.get().lifecycle
|
||||
private val lifecycleScope = lifecycle.coroutineScope
|
||||
|
||||
class EhApplication : Application(), SingletonImageLoader.Factory {
|
||||
class EhApplication :
|
||||
Application(),
|
||||
SingletonImageLoader.Factory {
|
||||
override fun onCreate() {
|
||||
// Initialize Settings on first access
|
||||
lifecycleScope.launchIO {
|
||||
|
||||
@@ -100,6 +100,7 @@ fun <T> PrefDelegate<T>.asMutableState(): MutableState<T> {
|
||||
object Settings : DataStorePreferences(null) {
|
||||
private const val KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations"
|
||||
|
||||
@Suppress("ktlint:standard:backing-property-naming")
|
||||
private val _favFlow = MutableSharedFlow<Unit>()
|
||||
val favChangesFlow = _favFlow.debounce(1000)
|
||||
var favCat by stringArrayPref("fav_cat", 10, "Favorites").emitTo(_favFlow)
|
||||
@@ -225,7 +226,7 @@ object Settings : DataStorePreferences(null) {
|
||||
private fun intArrayPref(key: String, count: Int) = object : Delegate<IntArray> {
|
||||
override val flowGetter: () -> Flow<Unit> = { _value.asFlow().flatMapMerge { it.changesFlow() }.conflate() }
|
||||
|
||||
@Suppress("ktlint:standard:property-naming")
|
||||
@Suppress("ktlint:standard:backing-property-naming")
|
||||
private var _value = (0 until count).map { intPref("${key}_$it", 0) }.toTypedArray()
|
||||
override fun getValue(thisRef: Any?, prop: KProperty<*>?): IntArray = _value.map { it.value }.toIntArray()
|
||||
override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: IntArray) {
|
||||
@@ -237,7 +238,7 @@ object Settings : DataStorePreferences(null) {
|
||||
private fun stringArrayPref(key: String, count: Int, defMetaValue: String) = object : Delegate<Array<String>> {
|
||||
override val flowGetter: () -> Flow<Unit> = { _value.asFlow().flatMapMerge { it.changesFlow() }.conflate() }
|
||||
|
||||
@Suppress("ktlint:standard:property-naming")
|
||||
@Suppress("ktlint:standard:backing-property-naming")
|
||||
private var _value = (0 until count).map { stringPref("${key}_$it", "$defMetaValue $it") }.toTypedArray()
|
||||
override fun getValue(thisRef: Any?, prop: KProperty<*>?): Array<String> = _value.map { it.value }.toTypedArray()
|
||||
override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: Array<String>) {
|
||||
|
||||
@@ -29,13 +29,9 @@ private const val URL_PREFIX_THUMB_E = "https://ehgt.org/"
|
||||
private const val URL_PREFIX_THUMB_EX = "https://s.exhentai.org/"
|
||||
private const val URL_PREFIX_LARGE_THUMB_EX = URL_PREFIX_THUMB_EX + "t/"
|
||||
|
||||
fun getImageKey(gid: Long, index: Int): String {
|
||||
return "image:$gid:$index"
|
||||
}
|
||||
fun getImageKey(gid: Long, index: Int): String = "image:$gid:$index"
|
||||
|
||||
fun getThumbKey(url: String): String {
|
||||
return url.removePrefix(URL_PREFIX_THUMB_E).removePrefix(URL_PREFIX_LARGE_THUMB_EX).removePrefix(URL_PREFIX_THUMB_EX)
|
||||
}
|
||||
fun getThumbKey(url: String): String = url.removePrefix(URL_PREFIX_THUMB_E).removePrefix(URL_PREFIX_LARGE_THUMB_EX).removePrefix(URL_PREFIX_THUMB_EX)
|
||||
|
||||
val GalleryPreview.url
|
||||
get() = if (exThumb) {
|
||||
|
||||
@@ -63,9 +63,7 @@ object EhTagDatabase : CoroutineScope {
|
||||
|
||||
var initialized by mutableStateOf(false)
|
||||
|
||||
fun getTranslation(prefix: String? = NAMESPACE_PREFIX, tag: String?): String? {
|
||||
return tagGroups[prefix]?.get(tag)?.trim()?.ifEmpty { null }
|
||||
}
|
||||
fun getTranslation(prefix: String? = NAMESPACE_PREFIX, tag: String?): String? = tagGroups[prefix]?.get(tag)?.trim()?.ifEmpty { null }
|
||||
|
||||
private fun internalSuggestFlow(
|
||||
tags: Map<String, String>,
|
||||
@@ -131,24 +129,16 @@ object EhTagDatabase : CoroutineScope {
|
||||
private fun String.containsIgnoreSpace(other: String, ignoreCase: Boolean = true): Boolean =
|
||||
removeSpace().contains(other.removeSpace(), ignoreCase)
|
||||
|
||||
private fun getMetadata(context: Context): Array<String>? {
|
||||
return context.resources.getStringArray(R.array.tag_translation_metadata)
|
||||
.takeIf { it.size == 4 }
|
||||
}
|
||||
private fun getMetadata(context: Context): Array<String>? = context.resources.getStringArray(R.array.tag_translation_metadata)
|
||||
.takeIf { it.size == 4 }
|
||||
|
||||
fun isTranslatable(context: Context): Boolean {
|
||||
return context.resources.getBoolean(R.bool.tag_translatable)
|
||||
}
|
||||
fun isTranslatable(context: Context): Boolean = context.resources.getBoolean(R.bool.tag_translatable)
|
||||
|
||||
private fun getFileContent(file: File): String? {
|
||||
return runCatching {
|
||||
file.source().buffer().use { it.readString(StandardCharsets.UTF_8) }
|
||||
}.getOrNull()
|
||||
}
|
||||
private fun getFileContent(file: File): String? = runCatching {
|
||||
file.source().buffer().use { it.readString(StandardCharsets.UTF_8) }
|
||||
}.getOrNull()
|
||||
|
||||
private fun checkData(sha1: String?, data: File): Boolean {
|
||||
return sha1 != null && sha1 == data.asUniFile().sha1()
|
||||
}
|
||||
private fun checkData(sha1: String?, data: File): Boolean = sha1 != null && sha1 == data.asUniFile().sha1()
|
||||
|
||||
private suspend fun save(client: HttpClient, url: String, file: File) {
|
||||
runCatching {
|
||||
|
||||
@@ -88,21 +88,18 @@ object EhUtils {
|
||||
|
||||
fun getCategory(type: String?): Int {
|
||||
for (entry in CATEGORY_STRINGS) {
|
||||
for (str in entry.first)
|
||||
for (str in entry.first) {
|
||||
if (str.equals(type, ignoreCase = true)) {
|
||||
return entry.second
|
||||
}
|
||||
}
|
||||
}
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
fun getCategory(type: Int): String {
|
||||
return CATEGORY_VALUES.getOrDefault(type, CATEGORY_VALUES[UNKNOWN])!![0]
|
||||
}
|
||||
fun getCategory(type: Int): String = CATEGORY_VALUES.getOrDefault(type, CATEGORY_VALUES[UNKNOWN])!![0]
|
||||
|
||||
private fun differenceDegrees(a: Float, b: Float): Float {
|
||||
return 180.0f - abs(abs(a - b) - 180.0f)
|
||||
}
|
||||
private fun differenceDegrees(a: Float, b: Float): Float = 180.0f - abs(abs(a - b) - 180.0f)
|
||||
|
||||
private fun sanitizeDegreesDouble(degrees: Float): Float {
|
||||
val deg = degrees % 360.0f
|
||||
@@ -159,13 +156,11 @@ object EhUtils {
|
||||
Settings.needSignIn = true
|
||||
}
|
||||
|
||||
fun getSuitableTitle(gi: GalleryInfo): String {
|
||||
return if (Settings.showJpnTitle) {
|
||||
if (gi.titleJpn.isNullOrEmpty()) gi.title else gi.titleJpn
|
||||
} else {
|
||||
if (gi.title.isNullOrEmpty()) gi.titleJpn else gi.title
|
||||
}.orEmpty()
|
||||
}
|
||||
fun getSuitableTitle(gi: GalleryInfo): String = if (Settings.showJpnTitle) {
|
||||
if (gi.titleJpn.isNullOrEmpty()) gi.title else gi.titleJpn
|
||||
} else {
|
||||
if (gi.title.isNullOrEmpty()) gi.titleJpn else gi.title
|
||||
}.orEmpty()
|
||||
|
||||
fun extractTitle(fullTitle: String?): String? {
|
||||
var title: String = fullTitle ?: return null
|
||||
|
||||
@@ -88,7 +88,8 @@ data class BaseGalleryInfo(
|
||||
|
||||
@Ignore
|
||||
override var favoriteNote: String? = null,
|
||||
) : GalleryInfo, Parcelable {
|
||||
) : GalleryInfo,
|
||||
Parcelable {
|
||||
constructor() : this(0, "")
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,8 @@ data class GalleryDetail(
|
||||
var comments: GalleryCommentList,
|
||||
val previewPages: Int,
|
||||
val previewList: List<GalleryPreview>,
|
||||
) : GalleryInfo by galleryInfo, Parcelable {
|
||||
) : GalleryInfo by galleryInfo,
|
||||
Parcelable {
|
||||
fun fillInfo() {
|
||||
val index = LANGUAGES.indexOf(language)
|
||||
if (index != -1) simpleLanguage = S_LANGS[index]
|
||||
|
||||
@@ -134,14 +134,10 @@ interface GalleryInfo {
|
||||
}
|
||||
}
|
||||
|
||||
fun GalleryInfo.findBaseInfo(): BaseGalleryInfo {
|
||||
return when (this) {
|
||||
is BaseGalleryInfo -> this
|
||||
is GalleryDetail -> galleryInfo
|
||||
else -> unreachable()
|
||||
}
|
||||
fun GalleryInfo.findBaseInfo(): BaseGalleryInfo = when (this) {
|
||||
is BaseGalleryInfo -> this
|
||||
is GalleryDetail -> galleryInfo
|
||||
else -> unreachable()
|
||||
}
|
||||
|
||||
fun GalleryInfo.asGalleryDetail(): GalleryDetail? {
|
||||
return this as? GalleryDetail
|
||||
}
|
||||
fun GalleryInfo.asGalleryDetail(): GalleryDetail? = this as? GalleryDetail
|
||||
@@ -22,4 +22,5 @@ import kotlinx.parcelize.Parcelize
|
||||
data class GalleryTagGroup(
|
||||
val groupName: String,
|
||||
private val tagList: MutableList<String> = mutableListOf(),
|
||||
) : Parcelable, MutableList<String> by tagList
|
||||
) : Parcelable,
|
||||
MutableList<String> by tagList
|
||||
@@ -83,18 +83,16 @@ data class ListUrlBuilder(
|
||||
mNext = q.name.substringAfterLast('@', "").ifEmpty { null }
|
||||
}
|
||||
|
||||
fun toQuickSearch(name: String): QuickSearch {
|
||||
return QuickSearch(
|
||||
name = name,
|
||||
mode = mode,
|
||||
category = category,
|
||||
keyword = mKeyword,
|
||||
advanceSearch = advanceSearch,
|
||||
minRating = minRating,
|
||||
pageFrom = pageFrom,
|
||||
pageTo = pageTo,
|
||||
)
|
||||
}
|
||||
fun toQuickSearch(name: String): QuickSearch = QuickSearch(
|
||||
name = name,
|
||||
mode = mode,
|
||||
category = category,
|
||||
keyword = mKeyword,
|
||||
advanceSearch = advanceSearch,
|
||||
minRating = minRating,
|
||||
pageFrom = pageFrom,
|
||||
pageTo = pageTo,
|
||||
)
|
||||
|
||||
fun equalsQuickSearch(q: QuickSearch?): Boolean {
|
||||
if (null == q) {
|
||||
@@ -259,85 +257,83 @@ data class ListUrlBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
fun build(): String {
|
||||
return when (mode) {
|
||||
MODE_NORMAL, MODE_SUBSCRIPTION -> ehUrl(EhUrl.WATCHED_PATH.takeIf { mode == MODE_SUBSCRIPTION }) {
|
||||
val category = if (!Settings.hasSignedIn.value && category <= 0) EhUtils.NON_H else category
|
||||
if (category > 0) {
|
||||
addQueryParameter("f_cats", (category.inv() and EhUtils.ALL_CATEGORY).toString())
|
||||
fun build(): String = when (mode) {
|
||||
MODE_NORMAL, MODE_SUBSCRIPTION -> ehUrl(EhUrl.WATCHED_PATH.takeIf { mode == MODE_SUBSCRIPTION }) {
|
||||
val category = if (!Settings.hasSignedIn.value && category <= 0) EhUtils.NON_H else category
|
||||
if (category > 0) {
|
||||
addQueryParameter("f_cats", (category.inv() and EhUtils.ALL_CATEGORY).toString())
|
||||
}
|
||||
val query = mKeyword?.let {
|
||||
val index = Settings.languageFilter.value
|
||||
GalleryInfo.S_LANG_TAGS.getOrNull(index)?.let { lang ->
|
||||
"$it $lang"
|
||||
} ?: it
|
||||
}
|
||||
addQueryParameterIfNotBlank("f_search", query)
|
||||
addQueryParameterIfNotBlank("prev", mPrev)
|
||||
addQueryParameterIfNotBlank("next", mNext)
|
||||
addQueryParameterIfNotBlank("seek", mJumpTo)
|
||||
addQueryParameterIfNotBlank("range", mRange.takeIf { it > 0 }?.toString())
|
||||
// Advance search
|
||||
if (advanceSearch > 0 || minRating > 0 || pageFrom > 0 || pageTo > 0) {
|
||||
addQueryParameter("advsearch", "1")
|
||||
if (advanceSearch and AdvanceTable.SH != 0) {
|
||||
addQueryParameter("f_sh", "on")
|
||||
}
|
||||
val query = mKeyword?.let {
|
||||
val index = Settings.languageFilter.value
|
||||
GalleryInfo.S_LANG_TAGS.getOrNull(index)?.let { lang ->
|
||||
"$it $lang"
|
||||
} ?: it
|
||||
if (advanceSearch and AdvanceTable.STO != 0) {
|
||||
addQueryParameter("f_sto", "on")
|
||||
}
|
||||
addQueryParameterIfNotBlank("f_search", query)
|
||||
if (advanceSearch and AdvanceTable.SFL != 0) {
|
||||
addQueryParameter("f_sfl", "on")
|
||||
}
|
||||
if (advanceSearch and AdvanceTable.SFU != 0) {
|
||||
addQueryParameter("f_sfu", "on")
|
||||
}
|
||||
if (advanceSearch and AdvanceTable.SFT != 0) {
|
||||
addQueryParameter("f_sft", "on")
|
||||
}
|
||||
// Set min star
|
||||
if (minRating > 0) {
|
||||
addQueryParameter("f_sr", "on")
|
||||
addQueryParameter("f_srdd", "$minRating")
|
||||
}
|
||||
// Pages
|
||||
if (pageFrom > 0 || pageTo > 0) {
|
||||
addQueryParameter("f_sp", "on")
|
||||
addQueryParameterIfNotBlank(
|
||||
"f_spf",
|
||||
pageFrom.takeIf { it > 0 }?.toString(),
|
||||
)
|
||||
addQueryParameterIfNotBlank(
|
||||
"f_spt",
|
||||
pageTo.takeIf { it > 0 }?.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}.buildString()
|
||||
|
||||
MODE_UPLOADER, MODE_TAG -> {
|
||||
val path = if (mode == MODE_UPLOADER) "uploader" else "tag"
|
||||
ehUrl(listOf(path, requireNotNull(mKeyword))) {
|
||||
addQueryParameterIfNotBlank("prev", mPrev)
|
||||
addQueryParameterIfNotBlank("next", mNext)
|
||||
addQueryParameterIfNotBlank("seek", mJumpTo)
|
||||
addQueryParameterIfNotBlank("range", mRange.takeIf { it > 0 }?.toString())
|
||||
// Advance search
|
||||
if (advanceSearch > 0 || minRating > 0 || pageFrom > 0 || pageTo > 0) {
|
||||
addQueryParameter("advsearch", "1")
|
||||
if (advanceSearch and AdvanceTable.SH != 0) {
|
||||
addQueryParameter("f_sh", "on")
|
||||
}
|
||||
if (advanceSearch and AdvanceTable.STO != 0) {
|
||||
addQueryParameter("f_sto", "on")
|
||||
}
|
||||
if (advanceSearch and AdvanceTable.SFL != 0) {
|
||||
addQueryParameter("f_sfl", "on")
|
||||
}
|
||||
if (advanceSearch and AdvanceTable.SFU != 0) {
|
||||
addQueryParameter("f_sfu", "on")
|
||||
}
|
||||
if (advanceSearch and AdvanceTable.SFT != 0) {
|
||||
addQueryParameter("f_sft", "on")
|
||||
}
|
||||
// Set min star
|
||||
if (minRating > 0) {
|
||||
addQueryParameter("f_sr", "on")
|
||||
addQueryParameter("f_srdd", "$minRating")
|
||||
}
|
||||
// Pages
|
||||
if (pageFrom > 0 || pageTo > 0) {
|
||||
addQueryParameter("f_sp", "on")
|
||||
addQueryParameterIfNotBlank(
|
||||
"f_spf",
|
||||
pageFrom.takeIf { it > 0 }?.toString(),
|
||||
)
|
||||
addQueryParameterIfNotBlank(
|
||||
"f_spt",
|
||||
pageTo.takeIf { it > 0 }?.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}.buildString()
|
||||
|
||||
MODE_UPLOADER, MODE_TAG -> {
|
||||
val path = if (mode == MODE_UPLOADER) "uploader" else "tag"
|
||||
ehUrl(listOf(path, requireNotNull(mKeyword))) {
|
||||
addQueryParameterIfNotBlank("prev", mPrev)
|
||||
addQueryParameterIfNotBlank("next", mNext)
|
||||
addQueryParameterIfNotBlank("seek", mJumpTo)
|
||||
addQueryParameterIfNotBlank("range", mRange.takeIf { it > 0 }?.toString())
|
||||
}.buildString()
|
||||
}
|
||||
|
||||
MODE_WHATS_HOT -> EhUrl.popularUrl
|
||||
MODE_IMAGE_SEARCH -> ehUrl {
|
||||
addQueryParameter("f_shash", requireNotNull(hash))
|
||||
}.buildString()
|
||||
MODE_TOPLIST -> {
|
||||
ehUrl("toplist.php", EhUrl.DOMAIN_E) {
|
||||
addQueryParameter("tl", requireNotNull(mKeyword))
|
||||
addQueryParameterIfNotBlank("p", mJumpTo)
|
||||
}.buildString()
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("Unexpected value: $mode")
|
||||
}
|
||||
|
||||
MODE_WHATS_HOT -> EhUrl.popularUrl
|
||||
MODE_IMAGE_SEARCH -> ehUrl {
|
||||
addQueryParameter("f_shash", requireNotNull(hash))
|
||||
}.buildString()
|
||||
MODE_TOPLIST -> {
|
||||
ehUrl("toplist.php", EhUrl.DOMAIN_E) {
|
||||
addQueryParameter("tl", requireNotNull(mKeyword))
|
||||
addQueryParameterIfNotBlank("p", mJumpTo)
|
||||
}.buildString()
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("Unexpected value: $mode")
|
||||
}
|
||||
|
||||
@IntDef(
|
||||
|
||||
@@ -20,7 +20,5 @@ package com.hippo.ehviewer.client.parser
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
object EventPaneParser {
|
||||
fun parse(body: String): String? {
|
||||
return Jsoup.parse(body).getElementById("eventpane")?.html()
|
||||
}
|
||||
fun parse(body: String): String? = Jsoup.parse(body).getElementById("eventpane")?.html()
|
||||
}
|
||||
@@ -17,12 +17,10 @@ data class FavParserResult(
|
||||
}
|
||||
|
||||
object FavoritesParser {
|
||||
fun parse(body: ByteBuffer): FavParserResult {
|
||||
return runSuspendCatching {
|
||||
unmarshalParsingAs<FavParserResult>(body, ::parseFav)
|
||||
}.getOrElse {
|
||||
throw ParseException("Parse favorites error", it)
|
||||
}
|
||||
fun parse(body: ByteBuffer): FavParserResult = runSuspendCatching {
|
||||
unmarshalParsingAs<FavParserResult>(body, ::parseFav)
|
||||
}.getOrElse {
|
||||
throw ParseException("Parse favorites error", it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,15 +21,13 @@ import com.hippo.ehviewer.util.ExceptionUtils
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
object ForumsParser {
|
||||
fun parse(body: String): String {
|
||||
return runCatching {
|
||||
val d = Jsoup.parse(body, EhUrl.URL_FORUMS)
|
||||
val userlinks = d.getElementById("userlinks")
|
||||
val child = userlinks!!.child(0).child(0).child(0)
|
||||
child.attr("href")
|
||||
}.getOrElse {
|
||||
ExceptionUtils.throwIfFatal(it)
|
||||
throw NotLoggedInException()
|
||||
}
|
||||
fun parse(body: String): String = runCatching {
|
||||
val d = Jsoup.parse(body, EhUrl.URL_FORUMS)
|
||||
val userlinks = d.getElementById("userlinks")
|
||||
val child = userlinks!!.child(0).child(0).child(0)
|
||||
child.attr("href")
|
||||
}.getOrElse {
|
||||
ExceptionUtils.throwIfFatal(it)
|
||||
throw NotLoggedInException()
|
||||
}
|
||||
}
|
||||
@@ -283,56 +283,50 @@ object GalleryDetailParser {
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseTagGroup(element: Element): GalleryTagGroup? {
|
||||
return try {
|
||||
var nameSpace = element.child(0).text()
|
||||
// Remove last ':'
|
||||
nameSpace = nameSpace.substring(0, nameSpace.length - 1)
|
||||
val group = GalleryTagGroup(nameSpace)
|
||||
val tags = element.child(1).children()
|
||||
tags.forEach {
|
||||
var tag = it.text()
|
||||
// Sometimes parody tag is followed with '|' and english translate, just remove them
|
||||
val index = tag.indexOf('|')
|
||||
if (index >= 0) {
|
||||
tag = tag.substring(0, index).trim()
|
||||
}
|
||||
if (it.className() == "gtw") {
|
||||
tag = "_$tag" // weak tag
|
||||
}
|
||||
group.add(tag)
|
||||
private fun parseTagGroup(element: Element): GalleryTagGroup? = try {
|
||||
var nameSpace = element.child(0).text()
|
||||
// Remove last ':'
|
||||
nameSpace = nameSpace.substring(0, nameSpace.length - 1)
|
||||
val group = GalleryTagGroup(nameSpace)
|
||||
val tags = element.child(1).children()
|
||||
tags.forEach {
|
||||
var tag = it.text()
|
||||
// Sometimes parody tag is followed with '|' and english translate, just remove them
|
||||
val index = tag.indexOf('|')
|
||||
if (index >= 0) {
|
||||
tag = tag.substring(0, index).trim()
|
||||
}
|
||||
if (group.size > 0) group else null
|
||||
} catch (e: Throwable) {
|
||||
ExceptionUtils.throwIfFatal(e)
|
||||
logcat(e)
|
||||
null
|
||||
if (it.className() == "gtw") {
|
||||
tag = "_$tag" // weak tag
|
||||
}
|
||||
group.add(tag)
|
||||
}
|
||||
if (group.size > 0) group else null
|
||||
} catch (e: Throwable) {
|
||||
ExceptionUtils.throwIfFatal(e)
|
||||
logcat(e)
|
||||
null
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse tag groups with html parser
|
||||
*/
|
||||
private fun parseTagGroups(document: Document): List<GalleryTagGroup> {
|
||||
return try {
|
||||
val taglist = document.getElementById("taglist")!!
|
||||
val tagGroups = taglist.child(0).child(0).children()
|
||||
parseTagGroups(tagGroups)
|
||||
} catch (e: Throwable) {
|
||||
ExceptionUtils.throwIfFatal(e)
|
||||
logcat(e)
|
||||
emptyList()
|
||||
}
|
||||
private fun parseTagGroups(document: Document): List<GalleryTagGroup> = try {
|
||||
val taglist = document.getElementById("taglist")!!
|
||||
val tagGroups = taglist.child(0).child(0).children()
|
||||
parseTagGroups(tagGroups)
|
||||
} catch (e: Throwable) {
|
||||
ExceptionUtils.throwIfFatal(e)
|
||||
logcat(e)
|
||||
emptyList()
|
||||
}
|
||||
|
||||
private fun parseTagGroups(trs: Elements): List<GalleryTagGroup> {
|
||||
return try {
|
||||
trs.mapNotNull { parseTagGroup(it) }
|
||||
} catch (e: Throwable) {
|
||||
ExceptionUtils.throwIfFatal(e)
|
||||
logcat(e)
|
||||
emptyList()
|
||||
}
|
||||
private fun parseTagGroups(trs: Elements): List<GalleryTagGroup> = try {
|
||||
trs.mapNotNull { parseTagGroup(it) }
|
||||
} catch (e: Throwable) {
|
||||
ExceptionUtils.throwIfFatal(e)
|
||||
logcat(e)
|
||||
emptyList()
|
||||
}
|
||||
|
||||
private suspend fun parseComment(element: Element): GalleryComment? {
|
||||
@@ -449,22 +443,16 @@ object GalleryDetailParser {
|
||||
/**
|
||||
* Parse preview pages with regular expressions
|
||||
*/
|
||||
fun parsePreviewPages(body: String): Int {
|
||||
return PATTERN_PREVIEW_PAGES.find(body)?.groupValues?.get(1)?.toIntOrNull()
|
||||
?: throw ParseException("Parse preview page count error")
|
||||
}
|
||||
fun parsePreviewPages(body: String): Int = PATTERN_PREVIEW_PAGES.find(body)?.groupValues?.get(1)?.toIntOrNull()
|
||||
?: throw ParseException("Parse preview page count error")
|
||||
|
||||
/**
|
||||
* Parse pages with regular expressions
|
||||
*/
|
||||
fun parsePages(body: String): Int {
|
||||
return PATTERN_PAGES.find(body)?.groupValues?.get(1)?.toIntOrNull()
|
||||
?: throw ParseException("Parse pages error")
|
||||
}
|
||||
fun parsePages(body: String): Int = PATTERN_PAGES.find(body)?.groupValues?.get(1)?.toIntOrNull()
|
||||
?: throw ParseException("Parse pages error")
|
||||
|
||||
fun parsePreviewList(body: String): Pair<List<GalleryPreview>, List<String>> {
|
||||
return runCatching { parseNormalPreview(body) }.getOrElse { parseLargePreview(body) }
|
||||
}
|
||||
fun parsePreviewList(body: String): Pair<List<GalleryPreview>, List<String>> = runCatching { parseNormalPreview(body) }.getOrElse { parseLargePreview(body) }
|
||||
|
||||
private fun parseLargePreview(body: String): Pair<List<GalleryPreview>, List<String>> {
|
||||
check(PATTERN_LARGE_PREVIEW.containsMatchIn(body))
|
||||
|
||||
@@ -22,14 +22,12 @@ import eu.kanade.tachiyomi.util.system.logcat
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
object GalleryNotAvailableParser {
|
||||
fun parse(body: String): String? {
|
||||
return runCatching {
|
||||
val e = Jsoup.parse(body).getElementsByClass("d").first()
|
||||
e!!.child(0).html().replace("<br>", "\n")
|
||||
}.getOrElse {
|
||||
ExceptionUtils.throwIfFatal(it)
|
||||
logcat(it)
|
||||
null
|
||||
}
|
||||
fun parse(body: String): String? = runCatching {
|
||||
val e = Jsoup.parse(body).getElementsByClass("d").first()
|
||||
e!!.child(0).html().replace("<br>", "\n")
|
||||
}.getOrElse {
|
||||
ExceptionUtils.throwIfFatal(it)
|
||||
logcat(it)
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -32,13 +32,11 @@ object GalleryTokenApiParser {
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
fun parse(body: String): String {
|
||||
return runCatching {
|
||||
body.parseAs<Result>().tokenList[0].token
|
||||
}.getOrElse {
|
||||
ExceptionUtils.throwIfFatal(it)
|
||||
throw EhException(body.parseAs<Error>().error)
|
||||
}
|
||||
fun parse(body: String): String = runCatching {
|
||||
body.parseAs<Result>().tokenList[0].token
|
||||
}.getOrElse {
|
||||
ExceptionUtils.throwIfFatal(it)
|
||||
throw EhException(body.parseAs<Error>().error)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -37,23 +37,13 @@ object ParserUtils {
|
||||
minute()
|
||||
}
|
||||
|
||||
fun formatDate(time: Long): String {
|
||||
return formatter.format(time.toLocalDateTime())
|
||||
}
|
||||
fun formatDate(time: Long): String = formatter.format(time.toLocalDateTime())
|
||||
|
||||
fun parseDate(str: String): Long {
|
||||
return formatter.parse(str).toEpochMillis()
|
||||
}
|
||||
fun parseDate(str: String): Long = formatter.parse(str).toEpochMillis()
|
||||
|
||||
fun trim(str: String?): String {
|
||||
return str?.unescapeXml()?.trim() ?: ""
|
||||
}
|
||||
fun trim(str: String?): String = str?.unescapeXml()?.trim() ?: ""
|
||||
|
||||
fun parseInt(str: String?, defValue: Int): Int {
|
||||
return trim(str).replace(",", "").toIntOrDefault(defValue)
|
||||
}
|
||||
fun parseInt(str: String?, defValue: Int): Int = trim(str).replace(",", "").toIntOrDefault(defValue)
|
||||
|
||||
fun parseLong(str: String?, defValue: Long): Long {
|
||||
return trim(str).replace(",", "").toLongOrDefault(defValue)
|
||||
}
|
||||
fun parseLong(str: String?, defValue: Long): Long = trim(str).replace(",", "").toLongOrDefault(defValue)
|
||||
}
|
||||
@@ -22,31 +22,29 @@ import eu.kanade.tachiyomi.util.system.logcat
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
object ProfileParser {
|
||||
fun parse(body: String): Result {
|
||||
return runCatching {
|
||||
val d = Jsoup.parse(body)
|
||||
val profilename = d.getElementById("profilename")
|
||||
val displayName = profilename!!.child(0).text()
|
||||
val avatar = runCatching {
|
||||
val avatar =
|
||||
profilename.nextElementSibling()!!.nextElementSibling()!!.child(0).attr("src")
|
||||
if (avatar.isEmpty()) {
|
||||
null
|
||||
} else if (!avatar.startsWith("http")) {
|
||||
EhUrl.URL_FORUMS + avatar
|
||||
} else {
|
||||
avatar
|
||||
}
|
||||
}.getOrElse {
|
||||
ExceptionUtils.throwIfFatal(it)
|
||||
logcat { "No avatar" }
|
||||
fun parse(body: String): Result = runCatching {
|
||||
val d = Jsoup.parse(body)
|
||||
val profilename = d.getElementById("profilename")
|
||||
val displayName = profilename!!.child(0).text()
|
||||
val avatar = runCatching {
|
||||
val avatar =
|
||||
profilename.nextElementSibling()!!.nextElementSibling()!!.child(0).attr("src")
|
||||
if (avatar.isEmpty()) {
|
||||
null
|
||||
} else if (!avatar.startsWith("http")) {
|
||||
EhUrl.URL_FORUMS + avatar
|
||||
} else {
|
||||
avatar
|
||||
}
|
||||
Result(displayName, avatar)
|
||||
}.getOrElse {
|
||||
ExceptionUtils.throwIfFatal(it)
|
||||
throw ParseException("Parse forums error")
|
||||
logcat { "No avatar" }
|
||||
null
|
||||
}
|
||||
Result(displayName, avatar)
|
||||
}.getOrElse {
|
||||
ExceptionUtils.throwIfFatal(it)
|
||||
throw ParseException("Parse forums error")
|
||||
}
|
||||
|
||||
class Result(val displayName: String?, val avatar: String?)
|
||||
|
||||
@@ -25,11 +25,9 @@ object SignInParser {
|
||||
"|<span class=\"postcolor\">(.+?)</span>",
|
||||
)
|
||||
|
||||
fun parse(body: String): String {
|
||||
return NAME_PATTERN.find(body)?.let {
|
||||
it.groupValues[1]
|
||||
} ?: ERROR_PATTERN.find(body)?.let {
|
||||
throw EhException(it.groupValues[1].ifEmpty { it.groupValues[2] })
|
||||
} ?: throw ParseException("Can't parse sign in")
|
||||
}
|
||||
fun parse(body: String): String = NAME_PATTERN.find(body)?.let {
|
||||
it.groupValues[1]
|
||||
} ?: ERROR_PATTERN.find(body)?.let {
|
||||
throw EhException(it.groupValues[1].ifEmpty { it.groupValues[2] })
|
||||
} ?: throw ParseException("Can't parse sign in")
|
||||
}
|
||||
@@ -21,7 +21,5 @@ import com.hippo.ehviewer.client.parseAs
|
||||
|
||||
object VoteTagParser {
|
||||
// {"error":"The tag \"neko\" is not allowed. Use character:neko or artist:neko"}
|
||||
fun parse(body: String): String? {
|
||||
return runCatching { body.parseAs<Error>().error }.getOrNull()
|
||||
}
|
||||
fun parse(body: String): String? = runCatching { body.parseAs<Error>().error }.getOrNull()
|
||||
}
|
||||
@@ -37,8 +37,7 @@ import splitties.init.appCtx
|
||||
|
||||
private const val CHANNEL_ID = "DailyCheckNotification"
|
||||
|
||||
class DailyCheckWork(context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
class DailyCheckWork(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
|
||||
override suspend fun doWork(): Result = withIOContext {
|
||||
checkDawn().onFailure {
|
||||
return@withIOContext Result.retry()
|
||||
|
||||
@@ -72,7 +72,8 @@ data class DownloadInfo(
|
||||
|
||||
@Embedded
|
||||
val downloadInfo: DownloadEntity = DownloadEntity(galleryInfo.gid),
|
||||
) : GalleryInfo by galleryInfo, AbstractDownloadInfo by downloadInfo {
|
||||
) : GalleryInfo by galleryInfo,
|
||||
AbstractDownloadInfo by downloadInfo {
|
||||
|
||||
companion object {
|
||||
const val STATE_INVALID = -1
|
||||
|
||||
@@ -46,7 +46,10 @@ import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class DownloadService : Service(), DownloadManager.DownloadListener, CoroutineScope {
|
||||
class DownloadService :
|
||||
Service(),
|
||||
DownloadManager.DownloadListener,
|
||||
CoroutineScope {
|
||||
override val coroutineContext = Dispatchers.IO + SupervisorJob()
|
||||
private val deferredMgr = async { DownloadManager.apply { setDownloadListener(this@DownloadService) } }
|
||||
private var mNotifyManager: NotificationManagerCompat? = null
|
||||
|
||||
@@ -149,19 +149,15 @@ class ArchivePageLoader(
|
||||
FileUtils.getNameFromFilename(file.name)!!
|
||||
}
|
||||
|
||||
override fun getImageExtension(index: Int): String {
|
||||
return getExtension(index)
|
||||
}
|
||||
override fun getImageExtension(index: Int): String = getExtension(index)
|
||||
|
||||
override fun save(index: Int, file: UniFile): Boolean {
|
||||
return runCatching {
|
||||
file.openFileDescriptor("w").use {
|
||||
extractToFd(index, it.fd)
|
||||
}
|
||||
}.getOrElse {
|
||||
logcat(it)
|
||||
false
|
||||
override fun save(index: Int, file: UniFile): Boolean = runCatching {
|
||||
file.openFileDescriptor("w").use {
|
||||
extractToFd(index, it.fd)
|
||||
}
|
||||
}.getOrElse {
|
||||
logcat(it)
|
||||
false
|
||||
}
|
||||
|
||||
override fun preloadPages(pages: List<Int>, pair: Pair<Int, Int>) {}
|
||||
|
||||
@@ -25,7 +25,8 @@ import com.hippo.ehviewer.spider.SpiderQueen.OnSpiderListener
|
||||
import com.hippo.unifile.UniFile
|
||||
|
||||
class EhPageLoader(private val mGalleryInfo: GalleryInfo, startPage: Int) :
|
||||
PageLoader2(mGalleryInfo.gid, startPage), OnSpiderListener {
|
||||
PageLoader2(mGalleryInfo.gid, startPage),
|
||||
OnSpiderListener {
|
||||
private lateinit var mSpiderQueen: SpiderQueen
|
||||
override fun start() {
|
||||
super.start()
|
||||
@@ -45,13 +46,9 @@ class EhPageLoader(private val mGalleryInfo: GalleryInfo, startPage: Int) :
|
||||
EhUtils.getSuitableTitle(mGalleryInfo)
|
||||
}
|
||||
|
||||
override fun getImageExtension(index: Int): String? {
|
||||
return mSpiderQueen.getExtension(index)
|
||||
}
|
||||
override fun getImageExtension(index: Int): String? = mSpiderQueen.getExtension(index)
|
||||
|
||||
override fun save(index: Int, file: UniFile): Boolean {
|
||||
return mSpiderQueen.save(index, file)
|
||||
}
|
||||
override fun save(index: Int, file: UniFile): Boolean = mSpiderQueen.save(index, file)
|
||||
|
||||
override val size: Int
|
||||
get() = mSpiderQueen.size
|
||||
@@ -64,9 +61,7 @@ class EhPageLoader(private val mGalleryInfo: GalleryInfo, startPage: Int) :
|
||||
mSpiderQueen.forceRequest(index, orgImg)
|
||||
}
|
||||
|
||||
override suspend fun awaitReady(): Boolean {
|
||||
return super.awaitReady() && mSpiderQueen.awaitReady()
|
||||
}
|
||||
override suspend fun awaitReady(): Boolean = super.awaitReady() && mSpiderQueen.awaitReady()
|
||||
|
||||
override val isReady: Boolean
|
||||
get() = ::mSpiderQueen.isInitialized && mSpiderQueen.isReady
|
||||
|
||||
@@ -27,7 +27,9 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
abstract class PageLoader2(private val gid: Long, var startPage: Int) : PageLoader(), CoroutineScope {
|
||||
abstract class PageLoader2(private val gid: Long, var startPage: Int) :
|
||||
PageLoader(),
|
||||
CoroutineScope {
|
||||
override val coroutineContext = Dispatchers.IO + Job()
|
||||
private val progressJob = launch(start = CoroutineStart.LAZY) {
|
||||
if (startPage == -1) {
|
||||
|
||||
@@ -7,17 +7,11 @@ import okio.source
|
||||
|
||||
fun readLegacySpiderInfo(inputStream: InputStream): SpiderInfo {
|
||||
val source = inputStream.source().buffer()
|
||||
fun read(): String {
|
||||
return source.readUtf8LineStrict()
|
||||
}
|
||||
fun read(): String = source.readUtf8LineStrict()
|
||||
|
||||
fun readInt(): Int {
|
||||
return read().toInt()
|
||||
}
|
||||
fun readInt(): Int = read().toInt()
|
||||
|
||||
fun readLong(): Long {
|
||||
return read().toLong()
|
||||
}
|
||||
fun readLong(): Long = read().toLong()
|
||||
repeat(2) { read() } // We assert that only info v2
|
||||
val gid = readLong()
|
||||
val token = read()
|
||||
|
||||
@@ -18,12 +18,10 @@ object DownloadInfoMagics {
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeMagicRequestOrUrl(encoded: String): Pair<String, String?> {
|
||||
return if (encoded.startsWith(DOWNLOAD_INFO_DIRNAME_URL_MAGIC)) {
|
||||
val (a, b) = encoded.removePrefix(DOWNLOAD_INFO_DIRNAME_URL_MAGIC).split(DOWNLOAD_INFO_DIRNAME_URL_SEPARATOR)
|
||||
a to b
|
||||
} else {
|
||||
encoded to null
|
||||
}
|
||||
fun decodeMagicRequestOrUrl(encoded: String): Pair<String, String?> = if (encoded.startsWith(DOWNLOAD_INFO_DIRNAME_URL_MAGIC)) {
|
||||
val (a, b) = encoded.removePrefix(DOWNLOAD_INFO_DIRNAME_URL_MAGIC).split(DOWNLOAD_INFO_DIRNAME_URL_SEPARATOR)
|
||||
a to b
|
||||
} else {
|
||||
encoded to null
|
||||
}
|
||||
}
|
||||
@@ -93,13 +93,9 @@ class SpiderDen(val info: GalleryInfo) {
|
||||
}
|
||||
|
||||
// Search in both directories to maintain compatibility
|
||||
private fun findImageFile(index: Int): UniFile? {
|
||||
return tempDownloadDir?.findImageFile(index) ?: downloadDir?.findImageFile(index)
|
||||
}
|
||||
private fun findImageFile(index: Int): UniFile? = tempDownloadDir?.findImageFile(index) ?: downloadDir?.findImageFile(index)
|
||||
|
||||
private fun containInDownloadDir(index: Int): Boolean {
|
||||
return findImageFile(index) != null
|
||||
}
|
||||
private fun containInDownloadDir(index: Int): Boolean = findImageFile(index) != null
|
||||
|
||||
private fun copyFromCacheToDownloadDir(index: Int): Boolean {
|
||||
val dir = imageDir ?: return false
|
||||
@@ -116,19 +112,17 @@ class SpiderDen(val info: GalleryInfo) {
|
||||
}.getOrDefault(false)
|
||||
}
|
||||
|
||||
operator fun contains(index: Int): Boolean {
|
||||
return when (mode) {
|
||||
SpiderQueen.MODE_READ -> {
|
||||
containInCache(index) || containInDownloadDir(index)
|
||||
}
|
||||
operator fun contains(index: Int): Boolean = when (mode) {
|
||||
SpiderQueen.MODE_READ -> {
|
||||
containInCache(index) || containInDownloadDir(index)
|
||||
}
|
||||
|
||||
SpiderQueen.MODE_DOWNLOAD -> {
|
||||
containInDownloadDir(index) || copyFromCacheToDownloadDir(index)
|
||||
}
|
||||
SpiderQueen.MODE_DOWNLOAD -> {
|
||||
containInDownloadDir(index) || copyFromCacheToDownloadDir(index)
|
||||
}
|
||||
|
||||
else -> {
|
||||
false
|
||||
}
|
||||
else -> {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,17 +131,11 @@ class SpiderDen(val info: GalleryInfo) {
|
||||
return sCache.remove(key)
|
||||
}
|
||||
|
||||
private fun removeFromDownloadDir(index: Int): Boolean {
|
||||
return findImageFile(index)?.delete() ?: false
|
||||
}
|
||||
private fun removeFromDownloadDir(index: Int): Boolean = findImageFile(index)?.delete() ?: false
|
||||
|
||||
fun remove(index: Int): Boolean {
|
||||
return removeFromCache(index) or removeFromDownloadDir(index)
|
||||
}
|
||||
fun remove(index: Int): Boolean = removeFromCache(index) or removeFromDownloadDir(index)
|
||||
|
||||
private fun findDownloadFileForIndex(index: Int, extension: String): UniFile? {
|
||||
return imageDir?.createFile(perFilename(index, extension))
|
||||
}
|
||||
private fun findDownloadFileForIndex(index: Int, extension: String): UniFile? = imageDir?.createFile(perFilename(index, extension))
|
||||
|
||||
suspend fun makeHttpCallAndSaveImage(
|
||||
index: Int,
|
||||
@@ -346,9 +334,7 @@ class SpiderDen(val info: GalleryInfo) {
|
||||
private val FileNameRegex = Regex("^\\d{8}\\.\\w{3,4}")
|
||||
private val FileHashRegex = Regex("/([0-9a-f]{40})(?:-\\d+){3}-\\w+")
|
||||
|
||||
fun perFilename(index: Int, extension: String = ""): String {
|
||||
return "%08d.%s".format(index + 1, extension)
|
||||
}
|
||||
fun perFilename(index: Int, extension: String = ""): String = "%08d.%s".format(index + 1, extension)
|
||||
|
||||
private fun UniFile.findImageFile(index: Int): UniFile? {
|
||||
val head = perFilename(index)
|
||||
|
||||
@@ -58,24 +58,20 @@ private val spiderInfoCache by lazy {
|
||||
}
|
||||
}
|
||||
|
||||
fun readFromCache(gid: Long): SpiderInfo? {
|
||||
return spiderInfoCache.read(gid.toString()) {
|
||||
runCatching {
|
||||
cbor.decodeFromByteArray<SpiderInfo>(data.toFile().readBytes())
|
||||
}.onFailure {
|
||||
logcat(it)
|
||||
}.getOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
fun readCompatFromUniFile(file: UniFile): SpiderInfo? {
|
||||
return runCatching {
|
||||
file.openInputStream().use {
|
||||
cbor.decodeFromByteArray<SpiderInfo>(it.readBytes())
|
||||
}
|
||||
}.getOrNull() ?: runCatching {
|
||||
file.openInputStream().use { readLegacySpiderInfo(it) }
|
||||
fun readFromCache(gid: Long): SpiderInfo? = spiderInfoCache.read(gid.toString()) {
|
||||
runCatching {
|
||||
cbor.decodeFromByteArray<SpiderInfo>(data.toFile().readBytes())
|
||||
}.onFailure {
|
||||
logcat(it)
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
fun readCompatFromUniFile(file: UniFile): SpiderInfo? = runCatching {
|
||||
file.openInputStream().use {
|
||||
cbor.decodeFromByteArray<SpiderInfo>(it.readBytes())
|
||||
}
|
||||
}.getOrNull() ?: runCatching {
|
||||
file.openInputStream().use { readLegacySpiderInfo(it) }
|
||||
}.getOrNull()
|
||||
|
||||
const val TOKEN_FAILED = "failed"
|
||||
@@ -320,16 +320,14 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
||||
}
|
||||
}
|
||||
|
||||
private fun readSpiderInfoFromLocal(): SpiderInfo? {
|
||||
return mSpiderDen.downloadDir?.run {
|
||||
findFile(SPIDER_INFO_FILENAME)?.let { file ->
|
||||
readCompatFromUniFile(file)?.takeIf {
|
||||
it.gid == galleryInfo.gid && it.token == galleryInfo.token
|
||||
}
|
||||
private fun readSpiderInfoFromLocal(): SpiderInfo? = mSpiderDen.downloadDir?.run {
|
||||
findFile(SPIDER_INFO_FILENAME)?.let { file ->
|
||||
readCompatFromUniFile(file)?.takeIf {
|
||||
it.gid == galleryInfo.gid && it.token == galleryInfo.token
|
||||
}
|
||||
}
|
||||
?: readFromCache(galleryInfo.gid)?.takeIf { it.gid == galleryInfo.gid && it.token == galleryInfo.token }
|
||||
}
|
||||
?: readFromCache(galleryInfo.gid)?.takeIf { it.gid == galleryInfo.gid && it.token == galleryInfo.token }
|
||||
|
||||
private fun readPreviews(body: String, index: Int, spiderInfo: SpiderInfo) {
|
||||
spiderInfo.previewPages = parsePreviewPages(body)
|
||||
@@ -349,21 +347,19 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun readSpiderInfoFromInternet(): SpiderInfo? {
|
||||
return runSuspendCatching {
|
||||
ehRequest(
|
||||
getGalleryDetailUrl(galleryInfo.gid, galleryInfo.token, 0, false),
|
||||
referer,
|
||||
).fetchUsingAsText {
|
||||
val pages = parsePages(this)
|
||||
val spiderInfo = SpiderInfo(galleryInfo.gid, galleryInfo.token, pages)
|
||||
readPreviews(this, 0, spiderInfo)
|
||||
spiderInfo
|
||||
}
|
||||
}.onFailure {
|
||||
logcat(it)
|
||||
}.getOrNull()
|
||||
}
|
||||
private suspend fun readSpiderInfoFromInternet(): SpiderInfo? = runSuspendCatching {
|
||||
ehRequest(
|
||||
getGalleryDetailUrl(galleryInfo.gid, galleryInfo.token, 0, false),
|
||||
referer,
|
||||
).fetchUsingAsText {
|
||||
val pages = parsePages(this)
|
||||
val spiderInfo = SpiderInfo(galleryInfo.gid, galleryInfo.token, pages)
|
||||
readPreviews(this, 0, spiderInfo)
|
||||
spiderInfo
|
||||
}
|
||||
}.onFailure {
|
||||
logcat(it)
|
||||
}.getOrNull()
|
||||
|
||||
suspend fun getPTokenFromMultiPageViewer(index: Int): String? {
|
||||
val spiderInfo = mSpiderInfo
|
||||
@@ -421,9 +417,7 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
||||
mSpiderInfo.saveToCache()
|
||||
}
|
||||
|
||||
private fun isStateDone(state: Int): Boolean {
|
||||
return state == STATE_FINISHED || state == STATE_FAILED
|
||||
}
|
||||
private fun isStateDone(state: Int): Boolean = state == STATE_FINISHED || state == STATE_FAILED
|
||||
|
||||
fun updatePageState(index: Int, @State state: Int, error: String? = null) {
|
||||
var oldState: Int
|
||||
|
||||
@@ -439,9 +439,7 @@ suspend fun DialogState.awaitSelectDate(): String? {
|
||||
title = R.string.go_to,
|
||||
yearRange = initial.year..yesterday.year,
|
||||
selectableDates = object : SelectableDates {
|
||||
override fun isSelectableDate(utcTimeMillis: Long): Boolean {
|
||||
return utcTimeMillis in dateRange
|
||||
}
|
||||
override fun isSelectableDate(utcTimeMillis: Long): Boolean = utcTimeMillis in dateRange
|
||||
},
|
||||
)
|
||||
val date = dateMillis?.run { toLocalDateTime().date.toString() }
|
||||
|
||||
@@ -98,10 +98,8 @@ fun rememberSliderPagerDoubleSyncState(
|
||||
lazyListState: LazyListState,
|
||||
pagerState: PagerState,
|
||||
pageLoader: PageLoader2,
|
||||
): SliderPagerDoubleSync {
|
||||
return remember {
|
||||
SliderPagerDoubleSync(lazyListState, pagerState, pageLoader)
|
||||
}
|
||||
): SliderPagerDoubleSync = remember {
|
||||
SliderPagerDoubleSync(lazyListState, pagerState, pageLoader)
|
||||
}
|
||||
|
||||
private fun smoothScroll(distance: Int) =
|
||||
|
||||
@@ -15,8 +15,10 @@ data class DownloadsFilterState(
|
||||
) : Parcelable
|
||||
|
||||
fun DownloadsFilterState.take(info: DownloadInfo) =
|
||||
mode.take(info, label) && (state == -1 || info.state == state) &&
|
||||
mode.take(info, label) &&
|
||||
(state == -1 || info.state == state) &&
|
||||
with(info) {
|
||||
title.containsIgnoreCase(keyword) || titleJpn.containsIgnoreCase(keyword) ||
|
||||
title.containsIgnoreCase(keyword) ||
|
||||
titleJpn.containsIgnoreCase(keyword) ||
|
||||
simpleTags?.any { it.containsIgnoreCase(keyword) } ?: false
|
||||
}
|
||||
@@ -184,21 +184,19 @@ data class TokenArgs(
|
||||
) : GalleryDetailScreenArgs
|
||||
|
||||
@StringRes
|
||||
private fun getRatingText(rating: Float): Int {
|
||||
return when ((rating * 2).roundToInt()) {
|
||||
0 -> R.string.rating0
|
||||
1 -> R.string.rating1
|
||||
2 -> R.string.rating2
|
||||
3 -> R.string.rating3
|
||||
4 -> R.string.rating4
|
||||
5 -> R.string.rating5
|
||||
6 -> R.string.rating6
|
||||
7 -> R.string.rating7
|
||||
8 -> R.string.rating8
|
||||
9 -> R.string.rating9
|
||||
10 -> R.string.rating10
|
||||
else -> R.string.rating_none
|
||||
}
|
||||
private fun getRatingText(rating: Float): Int = when ((rating * 2).roundToInt()) {
|
||||
0 -> R.string.rating0
|
||||
1 -> R.string.rating1
|
||||
2 -> R.string.rating2
|
||||
3 -> R.string.rating3
|
||||
4 -> R.string.rating4
|
||||
5 -> R.string.rating5
|
||||
6 -> R.string.rating6
|
||||
7 -> R.string.rating7
|
||||
8 -> R.string.rating8
|
||||
9 -> R.string.rating9
|
||||
10 -> R.string.rating10
|
||||
else -> R.string.rating_none
|
||||
}
|
||||
|
||||
private fun List<GalleryTagGroup>.getArtistTag(): String? {
|
||||
@@ -618,14 +616,12 @@ fun GalleryDetailScreen(args: GalleryDetailScreenArgs, navigator: DestinationsNa
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.size(keylineMargin))
|
||||
fun getAllRatingText(rating: Float, ratingCount: Int): String {
|
||||
return getString(
|
||||
R.string.rating_text,
|
||||
getString(getRatingText(rating)),
|
||||
rating,
|
||||
ratingCount,
|
||||
)
|
||||
}
|
||||
fun getAllRatingText(rating: Float, ratingCount: Int): String = getString(
|
||||
R.string.rating_text,
|
||||
getString(getRatingText(rating)),
|
||||
rating,
|
||||
ratingCount,
|
||||
)
|
||||
var ratingText by rememberSaveable {
|
||||
mutableStateOf(getAllRatingText(galleryDetail.rating, galleryDetail.ratingCount))
|
||||
}
|
||||
|
||||
@@ -298,22 +298,20 @@ fun SearchBarScreen(
|
||||
}
|
||||
}
|
||||
|
||||
fun wrapTagKeyword(keyword: String, translate: Boolean = false): String {
|
||||
return if (keyword.endsWith(':')) {
|
||||
keyword
|
||||
fun wrapTagKeyword(keyword: String, translate: Boolean = false): String = if (keyword.endsWith(':')) {
|
||||
keyword
|
||||
} else {
|
||||
val tag = keyword.substringAfter(':')
|
||||
val prefix = keyword.dropLast(tag.length + 1)
|
||||
if (translate) {
|
||||
val namespacePrefix = TagNamespace(prefix).toPrefix()
|
||||
val newPrefix = EhTagDatabase.getTranslation(tag = prefix) ?: prefix
|
||||
val newTag = EhTagDatabase.getTranslation(namespacePrefix, tag) ?: tag
|
||||
"$newPrefix:$newTag"
|
||||
} else if (keyword.contains(' ')) {
|
||||
"$prefix:\"$tag$\""
|
||||
} else {
|
||||
val tag = keyword.substringAfter(':')
|
||||
val prefix = keyword.dropLast(tag.length + 1)
|
||||
if (translate) {
|
||||
val namespacePrefix = TagNamespace(prefix).toPrefix()
|
||||
val newPrefix = EhTagDatabase.getTranslation(tag = prefix) ?: prefix
|
||||
val newTag = EhTagDatabase.getTranslation(namespacePrefix, tag) ?: tag
|
||||
"$newPrefix:$newTag"
|
||||
} else if (keyword.contains(' ')) {
|
||||
"$prefix:\"$tag$\""
|
||||
} else {
|
||||
"$keyword$"
|
||||
}
|
||||
"$keyword$"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,8 +129,9 @@ fun AnnotatedString.toBBCode() = buildString {
|
||||
val stack = ArrayDeque<AnnotatedString.Range<SpanStyle>>()
|
||||
val spans = spanStyles.groupBy { it.start }
|
||||
while (true) {
|
||||
while (stack.lastOrNull()?.end == current)
|
||||
while (stack.lastOrNull()?.end == current) {
|
||||
stack.removeLast().item.pop()
|
||||
}
|
||||
if (current == len) break
|
||||
spans[current]?.sortedByDescending { it.end }?.forEach {
|
||||
it.item.push()
|
||||
|
||||
@@ -209,61 +209,59 @@ class DialogState {
|
||||
@StringRes checkBoxText: Int,
|
||||
isNumber: Boolean = false,
|
||||
invalidator: (suspend Raise<String>.(String, Boolean) -> Unit)? = null,
|
||||
): Pair<String, Boolean> {
|
||||
return dialog { cont ->
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var state by remember(cont) { mutableStateOf(initial) }
|
||||
var error by remember(cont) { mutableStateOf<String?>(null) }
|
||||
var checkedState by remember { mutableStateOf(checked) }
|
||||
AlertDialog(
|
||||
onDismissRequest = { cont.cancel() },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
if (invalidator == null) {
|
||||
cont.resume(state to checkedState)
|
||||
} else {
|
||||
coroutineScope.launch {
|
||||
error = either { invalidator(state, checkedState) }.leftOrNull()
|
||||
error ?: cont.resume(state to checkedState)
|
||||
}
|
||||
): Pair<String, Boolean> = dialog { cont ->
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var state by remember(cont) { mutableStateOf(initial) }
|
||||
var error by remember(cont) { mutableStateOf<String?>(null) }
|
||||
var checkedState by remember { mutableStateOf(checked) }
|
||||
AlertDialog(
|
||||
onDismissRequest = { cont.cancel() },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
if (invalidator == null) {
|
||||
cont.resume(state to checkedState)
|
||||
} else {
|
||||
coroutineScope.launch {
|
||||
error = either { invalidator(state, checkedState) }.leftOrNull()
|
||||
error ?: cont.resume(state to checkedState)
|
||||
}
|
||||
}) {
|
||||
Text(text = stringResource(id = android.R.string.ok))
|
||||
}
|
||||
},
|
||||
title = title.ifNotNullThen { Text(text = stringResource(id = title!!)) },
|
||||
text = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = state,
|
||||
onValueChange = { state = it },
|
||||
label = hint.ifNotNullThen {
|
||||
Text(text = stringResource(id = hint!!))
|
||||
},
|
||||
trailingIcon = error.ifNotNullThen {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Info,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
supportingText = error.ifNotNullThen {
|
||||
Text(text = error!!)
|
||||
},
|
||||
isError = error != null,
|
||||
keyboardOptions = if (isNumber) KeyboardOptions(keyboardType = KeyboardType.Number) else KeyboardOptions.Default,
|
||||
)
|
||||
LabeledCheckbox(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
checked = checkedState,
|
||||
onCheckedChange = { checkedState = !checkedState },
|
||||
label = stringResource(checkBoxText),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}) {
|
||||
Text(text = stringResource(id = android.R.string.ok))
|
||||
}
|
||||
},
|
||||
title = title.ifNotNullThen { Text(text = stringResource(id = title!!)) },
|
||||
text = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = state,
|
||||
onValueChange = { state = it },
|
||||
label = hint.ifNotNullThen {
|
||||
Text(text = stringResource(id = hint!!))
|
||||
},
|
||||
trailingIcon = error.ifNotNullThen {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Info,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
supportingText = error.ifNotNullThen {
|
||||
Text(text = error!!)
|
||||
},
|
||||
isError = error != null,
|
||||
keyboardOptions = if (isNumber) KeyboardOptions(keyboardType = KeyboardType.Number) else KeyboardOptions.Default,
|
||||
)
|
||||
LabeledCheckbox(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
checked = checkedState,
|
||||
onCheckedChange = { checkedState = !checkedState },
|
||||
label = stringResource(checkBoxText),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitPermissionOrCancel(
|
||||
@@ -275,32 +273,30 @@ class DialogState {
|
||||
onCancelButtonClick: () -> Unit = {},
|
||||
secure: Boolean = false,
|
||||
text: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
return dialog { cont ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { cont.cancel() },
|
||||
confirmButton = {
|
||||
TextButton(onClick = { cont.resume(Unit) }, enabled = confirmButtonEnabled) {
|
||||
Text(text = stringResource(id = confirmText))
|
||||
}
|
||||
},
|
||||
dismissButton = showCancelButton.ifTrueThen {
|
||||
TextButton(onClick = {
|
||||
onCancelButtonClick()
|
||||
cont.cancel()
|
||||
}) {
|
||||
Text(text = stringResource(id = dismissText))
|
||||
}
|
||||
},
|
||||
title = title.ifNotNullThen { Text(text = stringResource(id = title!!)) },
|
||||
text = text,
|
||||
properties = if (secure) {
|
||||
DialogProperties(securePolicy = SecureFlagPolicy.SecureOn)
|
||||
} else {
|
||||
DialogProperties()
|
||||
},
|
||||
)
|
||||
}
|
||||
) = dialog { cont ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { cont.cancel() },
|
||||
confirmButton = {
|
||||
TextButton(onClick = { cont.resume(Unit) }, enabled = confirmButtonEnabled) {
|
||||
Text(text = stringResource(id = confirmText))
|
||||
}
|
||||
},
|
||||
dismissButton = showCancelButton.ifTrueThen {
|
||||
TextButton(onClick = {
|
||||
onCancelButtonClick()
|
||||
cont.cancel()
|
||||
}) {
|
||||
Text(text = stringResource(id = dismissText))
|
||||
}
|
||||
},
|
||||
title = title.ifNotNullThen { Text(text = stringResource(id = title!!)) },
|
||||
text = text,
|
||||
properties = if (secure) {
|
||||
DialogProperties(securePolicy = SecureFlagPolicy.SecureOn)
|
||||
} else {
|
||||
DialogProperties()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitSelectDate(
|
||||
@@ -311,63 +307,59 @@ class DialogState {
|
||||
initialDisplayMode: DisplayMode = DisplayMode.Picker,
|
||||
selectableDates: SelectableDates = DatePickerDefaults.AllDates,
|
||||
showModeToggle: Boolean = true,
|
||||
): Long? {
|
||||
return dialog { cont ->
|
||||
val state = rememberDatePickerState(
|
||||
initialSelectedDateMillis,
|
||||
initialDisplayedMonthMillis,
|
||||
yearRange,
|
||||
initialDisplayMode,
|
||||
selectableDates,
|
||||
): Long? = dialog { cont ->
|
||||
val state = rememberDatePickerState(
|
||||
initialSelectedDateMillis,
|
||||
initialDisplayedMonthMillis,
|
||||
yearRange,
|
||||
initialDisplayMode,
|
||||
selectableDates,
|
||||
)
|
||||
DatePickerDialog(
|
||||
onDismissRequest = { cont.cancel() },
|
||||
confirmButton = {
|
||||
TextButton(onClick = { cont.resume(state.selectedDateMillis) }) {
|
||||
Text(text = stringResource(id = android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { cont.cancel() }) {
|
||||
Text(text = stringResource(id = android.R.string.cancel))
|
||||
}
|
||||
},
|
||||
) {
|
||||
DatePicker(
|
||||
state = state,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = title),
|
||||
modifier = Modifier.padding(DatePickerTitlePadding),
|
||||
)
|
||||
},
|
||||
showModeToggle = showModeToggle,
|
||||
)
|
||||
DatePickerDialog(
|
||||
onDismissRequest = { cont.cancel() },
|
||||
confirmButton = {
|
||||
TextButton(onClick = { cont.resume(state.selectedDateMillis) }) {
|
||||
Text(text = stringResource(id = android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { cont.cancel() }) {
|
||||
Text(text = stringResource(id = android.R.string.cancel))
|
||||
}
|
||||
},
|
||||
) {
|
||||
DatePicker(
|
||||
state = state,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = title),
|
||||
modifier = Modifier.padding(DatePickerTitlePadding),
|
||||
)
|
||||
},
|
||||
showModeToggle = showModeToggle,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <R> showNoButton(respectDefaultWidth: Boolean = true, block: @Composable DismissDialogScope<R>.() -> Unit): R {
|
||||
return dialog { cont ->
|
||||
val impl = remember(cont) {
|
||||
DismissDialogScope<R> {
|
||||
cont.resume(it)
|
||||
}
|
||||
suspend fun <R> showNoButton(respectDefaultWidth: Boolean = true, block: @Composable DismissDialogScope<R>.() -> Unit): R = dialog { cont ->
|
||||
val impl = remember(cont) {
|
||||
DismissDialogScope<R> {
|
||||
cont.resume(it)
|
||||
}
|
||||
BasicAlertDialog(
|
||||
onDismissRequest = { cont.cancel() },
|
||||
properties = DialogProperties(usePlatformDefaultWidth = respectDefaultWidth),
|
||||
content = {
|
||||
Surface(
|
||||
modifier = with(Modifier) { if (!respectDefaultWidth) defaultMinSize(280.dp) else width(280.dp) },
|
||||
shape = AlertDialogDefaults.shape,
|
||||
color = AlertDialogDefaults.containerColor,
|
||||
tonalElevation = AlertDialogDefaults.TonalElevation,
|
||||
content = { block(impl) },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
BasicAlertDialog(
|
||||
onDismissRequest = { cont.cancel() },
|
||||
properties = DialogProperties(usePlatformDefaultWidth = respectDefaultWidth),
|
||||
content = {
|
||||
Surface(
|
||||
modifier = with(Modifier) { if (!respectDefaultWidth) defaultMinSize(280.dp) else width(280.dp) },
|
||||
shape = AlertDialogDefaults.shape,
|
||||
color = AlertDialogDefaults.containerColor,
|
||||
tonalElevation = AlertDialogDefaults.TonalElevation,
|
||||
content = { block(impl) },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitSelectTime(
|
||||
|
||||
@@ -55,10 +55,8 @@ fun MetaRatingWidget(rating: Float, ratingSize: Dp, ratingInterval: Dp, modifier
|
||||
val density = LocalDensity.current
|
||||
val ratingSizePx = remember { with(density) { ratingSize.toPx() } }
|
||||
val ratingIntervalPx = remember { with(density) { ratingInterval.toPx() } }
|
||||
fun calculateRating(offset: Float): Float {
|
||||
return ((offset * 2 + ratingIntervalPx) / (ratingSizePx + ratingIntervalPx))
|
||||
.roundToInt().coerceIn(0, 10).div(2f)
|
||||
}
|
||||
fun calculateRating(offset: Float): Float = ((offset * 2 + ratingIntervalPx) / (ratingSizePx + ratingIntervalPx))
|
||||
.roundToInt().coerceIn(0, 10).div(2f)
|
||||
Row(
|
||||
modifier = onRatingChange?.let {
|
||||
modifier
|
||||
|
||||
@@ -13,6 +13,4 @@ fun rememberLambda(calculation: @DisallowComposableCalls () -> Unit): () -> Unit
|
||||
fun rememberLambda(
|
||||
key1: Any?,
|
||||
calculation: @DisallowComposableCalls () -> Unit,
|
||||
): () -> Unit {
|
||||
return currentComposer.cache(currentComposer.changed(key1)) { calculation }
|
||||
}
|
||||
): () -> Unit = currentComposer.cache(currentComposer.changed(key1)) { calculation }
|
||||
@@ -199,8 +199,7 @@ private fun rememberColumnWidthSums(
|
||||
horizontalArrangement,
|
||||
contentPadding,
|
||||
) {
|
||||
{
|
||||
constraints ->
|
||||
{ constraints ->
|
||||
require(constraints.maxWidth != Constraints.Infinity) {
|
||||
"LazyVerticalGrid's width should be bound by parent"
|
||||
}
|
||||
@@ -230,8 +229,7 @@ private fun rememberColumnWidthSums(
|
||||
horizontalArrangement,
|
||||
contentPadding,
|
||||
) {
|
||||
{
|
||||
constraints ->
|
||||
{ constraints ->
|
||||
require(constraints.maxWidth != Constraints.Infinity) {
|
||||
"LazyVerticalGrid's width should be bound by parent"
|
||||
}
|
||||
|
||||
@@ -57,9 +57,7 @@ object AppConfig {
|
||||
val defaultDownloadDir: File?
|
||||
get() = getDirInExternalAppDir(DOWNLOAD, false)
|
||||
|
||||
fun getTempDir(filename: String): UniFile? {
|
||||
return getDirInExternalAppDir(TEMP)?.run { File(this, filename).asUniFile() }
|
||||
}
|
||||
fun getTempDir(filename: String): UniFile? = getDirInExternalAppDir(TEMP)?.run { File(this, filename).asUniFile() }
|
||||
|
||||
val externalTempDir: File?
|
||||
get() = appCtx.externalCacheDir?.run { File(this, TEMP).takeIf { it.ensureDirectory() } }
|
||||
@@ -71,9 +69,7 @@ object AppConfig {
|
||||
val tempDir: File?
|
||||
get() = appCtx.cacheDir.run { File(this, TEMP).takeIf { it.ensureDirectory() } }
|
||||
|
||||
fun createTempFile(): File? {
|
||||
return FileUtils.createTempFile(tempDir, null)
|
||||
}
|
||||
fun createTempFile(): File? = FileUtils.createTempFile(tempDir, null)
|
||||
|
||||
fun getFilesDir(name: String) = File(appCtx.filesDir, name).takeIf { it.ensureDirectory() }
|
||||
}
|
||||
@@ -8,11 +8,10 @@ inline fun <R : Any, C : MutableCollection<in R>> LongArray.mapNotNullTo(destina
|
||||
inline fun <T> Collection<T>.mapToLongArray(transform: (T) -> Long): LongArray {
|
||||
val result = LongArray(size)
|
||||
var index = 0
|
||||
for (element in this)
|
||||
for (element in this) {
|
||||
result[index++] = transform(element)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
inline fun <R : Any> LongArray.mapNotNull(transform: (Long) -> R?): List<R> {
|
||||
return mapNotNullTo(ArrayList(), transform)
|
||||
}
|
||||
inline fun <R : Any> LongArray.mapNotNull(transform: (Long) -> R?): List<R> = mapNotNullTo(ArrayList(), transform)
|
||||
@@ -9,14 +9,10 @@ import java.io.FileWriter
|
||||
import java.io.OutputStreamWriter
|
||||
import java.io.PrintWriter
|
||||
|
||||
private fun joinIfStringArray(any: Any?): String {
|
||||
return if (any is Array<*>) any.joinToString() else any.toString()
|
||||
}
|
||||
private fun joinIfStringArray(any: Any?): String = if (any is Array<*>) any.joinToString() else any.toString()
|
||||
|
||||
private fun collectClassStaticInfo(clazz: Class<*>): String {
|
||||
return clazz.fields.joinToString("\n") {
|
||||
"${it.name}=${joinIfStringArray(it.get(null))}"
|
||||
}
|
||||
private fun collectClassStaticInfo(clazz: Class<*>): String = clazz.fields.joinToString("\n") {
|
||||
"${it.name}=${joinIfStringArray(it.get(null))}"
|
||||
}
|
||||
|
||||
object Crash {
|
||||
|
||||
@@ -57,7 +57,8 @@ suspend fun <I, O> Context.awaitActivityResult(contract: ActivityResultContract<
|
||||
}
|
||||
return withUIContext {
|
||||
lifecycle.addObserver(observer)
|
||||
suspendCoroutine { cont -> // No cancellation support here since we cannot cancel a launched Intent
|
||||
suspendCoroutine { cont ->
|
||||
// No cancellation support here since we cannot cancel a launched Intent
|
||||
val activity = findActivity<ComponentActivity>()
|
||||
launcher = activity.activityResultRegistry.register(key, contract) {
|
||||
launcher?.unregister()
|
||||
|
||||
@@ -34,7 +34,9 @@ package com.hippo.ehviewer.util
|
||||
*
|
||||
* @param T the type of ranges in this set
|
||||
*/
|
||||
abstract class RangeSet<T : Comparable<T>> : MutableSet<ClosedRange<T>>, Cloneable {
|
||||
abstract class RangeSet<T : Comparable<T>> :
|
||||
MutableSet<ClosedRange<T>>,
|
||||
Cloneable {
|
||||
private val ranges = ArrayDeque<ClosedRange<T>>()
|
||||
|
||||
/**
|
||||
@@ -93,10 +95,11 @@ abstract class RangeSet<T : Comparable<T>> : MutableSet<ClosedRange<T>>, Cloneab
|
||||
* @return whether all values are all contained within the set
|
||||
*/
|
||||
override fun contains(element: ClosedRange<T>): Boolean {
|
||||
for (range in ranges)
|
||||
for (range in ranges) {
|
||||
if (element.start >= range.start && element.endInclusive <= range.endInclusive) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -218,9 +221,7 @@ abstract class RangeSet<T : Comparable<T>> : MutableSet<ClosedRange<T>>, Cloneab
|
||||
* @param elements ranges of values to add
|
||||
* @return whether any values were added
|
||||
*/
|
||||
override fun addAll(elements: Collection<ClosedRange<T>>): Boolean {
|
||||
return elements.map { add(it) }.any()
|
||||
}
|
||||
override fun addAll(elements: Collection<ClosedRange<T>>): Boolean = elements.map { add(it) }.any()
|
||||
|
||||
/**
|
||||
* Removes all values from the set.
|
||||
@@ -319,9 +320,7 @@ abstract class RangeSet<T : Comparable<T>> : MutableSet<ClosedRange<T>>, Cloneab
|
||||
* @param elements ranges of values to remove
|
||||
* @return whether any values were removed
|
||||
*/
|
||||
override fun removeAll(elements: Collection<ClosedRange<T>>): Boolean {
|
||||
return elements.map { remove(it) }.any { it }
|
||||
}
|
||||
override fun removeAll(elements: Collection<ClosedRange<T>>): Boolean = elements.map { remove(it) }.any { it }
|
||||
|
||||
/**
|
||||
* Removes all values from set except those contained in the specified range; intersection set logic.
|
||||
@@ -441,9 +440,7 @@ abstract class RangeSet<T : Comparable<T>> : MutableSet<ClosedRange<T>>, Cloneab
|
||||
*
|
||||
* @return new set containing values not present in this set within the specified range
|
||||
*/
|
||||
fun difference(element: ClosedRange<T>): RangeSet<T> {
|
||||
return differenceAll(listOf(element))
|
||||
}
|
||||
fun difference(element: ClosedRange<T>): RangeSet<T> = differenceAll(listOf(element))
|
||||
|
||||
/**
|
||||
* Creates a new [RangeSet] containing values not present in this set within the specified ranges.
|
||||
@@ -476,19 +473,13 @@ abstract class RangeSet<T : Comparable<T>> : MutableSet<ClosedRange<T>>, Cloneab
|
||||
*
|
||||
* @return new set containing missing values between the ranges of this set
|
||||
*/
|
||||
fun gaps(): RangeSet<T> {
|
||||
return if (ranges.isEmpty()) clone().apply { clear() } else difference(ranges.first().endInclusive..ranges.last().start)
|
||||
}
|
||||
fun gaps(): RangeSet<T> = if (ranges.isEmpty()) clone().apply { clear() } else difference(ranges.first().endInclusive..ranges.last().start)
|
||||
|
||||
override fun iterator(): MutableIterator<ClosedRange<T>> = ranges.iterator()
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return ranges.hashCode()
|
||||
}
|
||||
override fun hashCode(): Int = ranges.hashCode()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return this === other || (other is RangeSet<*> && ranges == other.ranges)
|
||||
}
|
||||
override fun equals(other: Any?): Boolean = this === other || (other is RangeSet<*> && ranges == other.ranges)
|
||||
|
||||
protected abstract fun createRange(start: T, endInclusive: T): ClosedRange<T>
|
||||
|
||||
|
||||
@@ -143,17 +143,15 @@ object ReadableTime {
|
||||
}
|
||||
}
|
||||
|
||||
fun getShortTimeInterval(time: Long): String {
|
||||
return buildString {
|
||||
for (i in 0 until SIZE) {
|
||||
val multiple = MULTIPLES[i]
|
||||
val quotient = time / multiple
|
||||
if (time > multiple * 1.5 || i == SIZE - 1) {
|
||||
append(quotient)
|
||||
.append(" ")
|
||||
.append(resources.getQuantityString(UNITS[i], quotient.toInt()))
|
||||
break
|
||||
}
|
||||
fun getShortTimeInterval(time: Long): String = buildString {
|
||||
for (i in 0 until SIZE) {
|
||||
val multiple = MULTIPLES[i]
|
||||
val quotient = time / multiple
|
||||
if (time > multiple * 1.5 || i == SIZE - 1) {
|
||||
append(quotient)
|
||||
.append(" ")
|
||||
.append(resources.getQuantityString(UNITS[i], quotient.toInt()))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,7 @@ class StatusCodeException(val responseCode: Int) : Exception() {
|
||||
val isIdentifiedResponseCode: Boolean
|
||||
get() = DEFAULT_ERROR_MESSAGE != message
|
||||
|
||||
override fun getLocalizedMessage(): String {
|
||||
return message
|
||||
}
|
||||
override fun getLocalizedMessage(): String = message
|
||||
|
||||
companion object {
|
||||
private val ERROR_MESSAGE_ARRAY = SparseArray<String>(24)
|
||||
|
||||
@@ -2,24 +2,14 @@ package com.hippo.ehviewer.util
|
||||
|
||||
import org.jsoup.parser.Parser
|
||||
|
||||
fun String.unescapeXml(): String {
|
||||
return Parser.unescapeEntities(this, true)
|
||||
}
|
||||
fun String.unescapeXml(): String = Parser.unescapeEntities(this, true)
|
||||
|
||||
inline infix fun <T> CharSequence.trimAnd(block: CharSequence.() -> T): T {
|
||||
return block(trim())
|
||||
}
|
||||
inline infix fun <T> CharSequence.trimAnd(block: CharSequence.() -> T): T = block(trim())
|
||||
|
||||
fun String.toIntOrDefault(defaultValue: Int): Int {
|
||||
return toIntOrNull() ?: defaultValue
|
||||
}
|
||||
fun String.toIntOrDefault(defaultValue: Int): Int = toIntOrNull() ?: defaultValue
|
||||
|
||||
fun String.toLongOrDefault(defaultValue: Long): Long {
|
||||
return toLongOrNull() ?: defaultValue
|
||||
}
|
||||
fun String.toLongOrDefault(defaultValue: Long): Long = toLongOrNull() ?: defaultValue
|
||||
|
||||
fun String.toFloatOrDefault(defaultValue: Float): Float {
|
||||
return toFloatOrNull() ?: defaultValue
|
||||
}
|
||||
fun String.toFloatOrDefault(defaultValue: Float): Float = toFloatOrNull() ?: defaultValue
|
||||
|
||||
fun String?.containsIgnoreCase(other: String) = this?.contains(other, true) ?: false
|
||||
@@ -28,21 +28,17 @@ object DocumentsContractApi21 {
|
||||
|
||||
fun createDirectory(self: Uri, displayName: String) = createFile(self, DocumentsContract.Document.MIME_TYPE_DIR, displayName)
|
||||
|
||||
fun prepareTreeUri(treeUri: Uri): Uri {
|
||||
return DocumentsContract.buildDocumentUriUsingTree(
|
||||
treeUri,
|
||||
DocumentsContract.getTreeDocumentId(treeUri),
|
||||
)
|
||||
}
|
||||
fun prepareTreeUri(treeUri: Uri): Uri = DocumentsContract.buildDocumentUriUsingTree(
|
||||
treeUri,
|
||||
DocumentsContract.getTreeDocumentId(treeUri),
|
||||
)
|
||||
|
||||
// This is not a good approach as the document identifier is an opaque implementation detail of
|
||||
// the provider, but works for ExternalStorageProvider
|
||||
fun buildChildUri(uri: Uri, displayName: String): Uri {
|
||||
return DocumentsContract.buildDocumentUriUsingTree(
|
||||
uri,
|
||||
DocumentsContract.getDocumentId(uri) + "/" + displayName,
|
||||
)
|
||||
}
|
||||
fun buildChildUri(uri: Uri, displayName: String): Uri = DocumentsContract.buildDocumentUriUsingTree(
|
||||
uri,
|
||||
DocumentsContract.getDocumentId(uri) + "/" + displayName,
|
||||
)
|
||||
|
||||
fun renameTo(self: Uri, displayName: String) = runCatching {
|
||||
DocumentsContract.renameDocument(resolver, self, displayName)
|
||||
|
||||
@@ -14,16 +14,12 @@ import okio.Path
|
||||
/**
|
||||
* Use Native IO/NIO directly if possible, unless you need process file content on JVM!
|
||||
*/
|
||||
fun UniFile.openInputStream(): FileInputStream {
|
||||
return AutoCloseInputStream(openFileDescriptor("r"))
|
||||
}
|
||||
fun UniFile.openInputStream(): FileInputStream = AutoCloseInputStream(openFileDescriptor("r"))
|
||||
|
||||
/**
|
||||
* Use Native IO/NIO directly if possible, unless you need process file content on JVM!
|
||||
*/
|
||||
fun UniFile.openOutputStream(): FileOutputStream {
|
||||
return AutoCloseOutputStream(openFileDescriptor("wt"))
|
||||
}
|
||||
fun UniFile.openOutputStream(): FileOutputStream = AutoCloseOutputStream(openFileDescriptor("wt"))
|
||||
|
||||
fun UniFile.sha1() = openFileDescriptor("r").use { sha1(it.fd) }
|
||||
|
||||
|
||||
@@ -10,10 +10,6 @@ fun Any?.ifNotNullThen(content: @Composable () -> Unit): (@Composable () -> Unit
|
||||
fun Boolean.ifTrueThen(content: @Composable () -> Unit): (@Composable () -> Unit)? =
|
||||
if (this) content else null
|
||||
|
||||
fun roundToDP(value: Double, places: Int): Double {
|
||||
return BigDecimal(value).setScale(places, RoundingMode.HALF_EVEN).toDouble()
|
||||
}
|
||||
fun roundToDP(value: Double, places: Int): Double = BigDecimal(value).setScale(places, RoundingMode.HALF_EVEN).toDouble()
|
||||
|
||||
fun roundToDP(value: Float, places: Int): Float {
|
||||
return BigDecimal(value.toDouble()).setScale(places, RoundingMode.HALF_EVEN).toFloat()
|
||||
}
|
||||
fun roundToDP(value: Float, places: Int): Float = BigDecimal(value.toDouble()).setScale(places, RoundingMode.HALF_EVEN).toFloat()
|
||||
@@ -24,22 +24,16 @@ sealed class AndroidPreference<T>(
|
||||
|
||||
abstract fun write(key: String, value: T): Editor.() -> Unit
|
||||
|
||||
override fun key(): String {
|
||||
return key
|
||||
}
|
||||
override fun key(): String = key
|
||||
|
||||
override fun get(): T {
|
||||
return read(preferences, key, defaultValue)
|
||||
}
|
||||
override fun get(): T = read(preferences, key, defaultValue)
|
||||
|
||||
// Async, don't call get() immediately after this
|
||||
override fun set(value: T) {
|
||||
preferences.edit(action = write(key, value))
|
||||
}
|
||||
|
||||
override fun isSet(): Boolean {
|
||||
return preferences.contains(key)
|
||||
}
|
||||
override fun isSet(): Boolean = preferences.contains(key)
|
||||
|
||||
override fun delete() {
|
||||
preferences.edit {
|
||||
@@ -47,21 +41,15 @@ sealed class AndroidPreference<T>(
|
||||
}
|
||||
}
|
||||
|
||||
override fun defaultValue(): T {
|
||||
return defaultValue
|
||||
}
|
||||
override fun defaultValue(): T = defaultValue
|
||||
|
||||
override fun changes(): Flow<T> {
|
||||
return keyFlow
|
||||
.filter { it == key || it == null }
|
||||
.onStart { emit("ignition") }
|
||||
.map { get() }
|
||||
.conflate()
|
||||
}
|
||||
override fun changes(): Flow<T> = keyFlow
|
||||
.filter { it == key || it == null }
|
||||
.onStart { emit("ignition") }
|
||||
.map { get() }
|
||||
.conflate()
|
||||
|
||||
override fun stateIn(scope: CoroutineScope): StateFlow<T> {
|
||||
return changes().stateIn(scope, SharingStarted.Eagerly, get())
|
||||
}
|
||||
override fun stateIn(scope: CoroutineScope): StateFlow<T> = changes().stateIn(scope, SharingStarted.Eagerly, get())
|
||||
|
||||
class StringPrimitive(
|
||||
preferences: SharedPreferences,
|
||||
@@ -69,9 +57,7 @@ sealed class AndroidPreference<T>(
|
||||
key: String,
|
||||
defaultValue: String,
|
||||
) : AndroidPreference<String>(preferences, keyFlow, key, defaultValue) {
|
||||
override fun read(preferences: SharedPreferences, key: String, defaultValue: String): String {
|
||||
return preferences.getString(key, defaultValue) ?: defaultValue
|
||||
}
|
||||
override fun read(preferences: SharedPreferences, key: String, defaultValue: String): String = preferences.getString(key, defaultValue) ?: defaultValue
|
||||
|
||||
override fun write(key: String, value: String): Editor.() -> Unit = {
|
||||
putString(key, value)
|
||||
@@ -84,9 +70,7 @@ sealed class AndroidPreference<T>(
|
||||
key: String,
|
||||
defaultValue: Long,
|
||||
) : AndroidPreference<Long>(preferences, keyFlow, key, defaultValue) {
|
||||
override fun read(preferences: SharedPreferences, key: String, defaultValue: Long): Long {
|
||||
return preferences.getLong(key, defaultValue)
|
||||
}
|
||||
override fun read(preferences: SharedPreferences, key: String, defaultValue: Long): Long = preferences.getLong(key, defaultValue)
|
||||
|
||||
override fun write(key: String, value: Long): Editor.() -> Unit = {
|
||||
putLong(key, value)
|
||||
@@ -99,9 +83,7 @@ sealed class AndroidPreference<T>(
|
||||
key: String,
|
||||
defaultValue: Int,
|
||||
) : AndroidPreference<Int>(preferences, keyFlow, key, defaultValue) {
|
||||
override fun read(preferences: SharedPreferences, key: String, defaultValue: Int): Int {
|
||||
return preferences.getInt(key, defaultValue)
|
||||
}
|
||||
override fun read(preferences: SharedPreferences, key: String, defaultValue: Int): Int = preferences.getInt(key, defaultValue)
|
||||
|
||||
override fun write(key: String, value: Int): Editor.() -> Unit = {
|
||||
putInt(key, value)
|
||||
@@ -114,9 +96,7 @@ sealed class AndroidPreference<T>(
|
||||
key: String,
|
||||
defaultValue: Float,
|
||||
) : AndroidPreference<Float>(preferences, keyFlow, key, defaultValue) {
|
||||
override fun read(preferences: SharedPreferences, key: String, defaultValue: Float): Float {
|
||||
return preferences.getFloat(key, defaultValue)
|
||||
}
|
||||
override fun read(preferences: SharedPreferences, key: String, defaultValue: Float): Float = preferences.getFloat(key, defaultValue)
|
||||
|
||||
override fun write(key: String, value: Float): Editor.() -> Unit = {
|
||||
putFloat(key, value)
|
||||
@@ -129,9 +109,7 @@ sealed class AndroidPreference<T>(
|
||||
key: String,
|
||||
defaultValue: Boolean,
|
||||
) : AndroidPreference<Boolean>(preferences, keyFlow, key, defaultValue) {
|
||||
override fun read(preferences: SharedPreferences, key: String, defaultValue: Boolean): Boolean {
|
||||
return preferences.getBoolean(key, defaultValue)
|
||||
}
|
||||
override fun read(preferences: SharedPreferences, key: String, defaultValue: Boolean): Boolean = preferences.getBoolean(key, defaultValue)
|
||||
|
||||
override fun write(key: String, value: Boolean): Editor.() -> Unit = {
|
||||
putBoolean(key, value)
|
||||
@@ -144,9 +122,7 @@ sealed class AndroidPreference<T>(
|
||||
key: String,
|
||||
defaultValue: Set<String>,
|
||||
) : AndroidPreference<Set<String>>(preferences, keyFlow, key, defaultValue) {
|
||||
override fun read(preferences: SharedPreferences, key: String, defaultValue: Set<String>): Set<String> {
|
||||
return preferences.getStringSet(key, defaultValue) ?: defaultValue
|
||||
}
|
||||
override fun read(preferences: SharedPreferences, key: String, defaultValue: Set<String>): Set<String> = preferences.getStringSet(key, defaultValue) ?: defaultValue
|
||||
|
||||
override fun write(key: String, value: Set<String>): Editor.() -> Unit = {
|
||||
putStringSet(key, value)
|
||||
@@ -161,12 +137,10 @@ sealed class AndroidPreference<T>(
|
||||
val serializer: (T) -> String,
|
||||
val deserializer: (String) -> T,
|
||||
) : AndroidPreference<T>(preferences, keyFlow, key, defaultValue) {
|
||||
override fun read(preferences: SharedPreferences, key: String, defaultValue: T): T {
|
||||
return try {
|
||||
preferences.getString(key, null)?.let(deserializer) ?: defaultValue
|
||||
} catch (e: Exception) {
|
||||
defaultValue
|
||||
}
|
||||
override fun read(preferences: SharedPreferences, key: String, defaultValue: T): T = try {
|
||||
preferences.getString(key, null)?.let(deserializer) ?: defaultValue
|
||||
} catch (e: Exception) {
|
||||
defaultValue
|
||||
}
|
||||
|
||||
override fun write(key: String, value: T): Editor.() -> Unit = {
|
||||
|
||||
@@ -18,45 +18,31 @@ object AndroidPreferenceStore : PreferenceStore {
|
||||
|
||||
private val keyFlow = sharedPreferences.keyFlow
|
||||
|
||||
override fun getString(key: String, defaultValue: String): Preference<String> {
|
||||
return StringPrimitive(sharedPreferences, keyFlow, key, defaultValue)
|
||||
}
|
||||
override fun getString(key: String, defaultValue: String): Preference<String> = StringPrimitive(sharedPreferences, keyFlow, key, defaultValue)
|
||||
|
||||
override fun getLong(key: String, defaultValue: Long): Preference<Long> {
|
||||
return LongPrimitive(sharedPreferences, keyFlow, key, defaultValue)
|
||||
}
|
||||
override fun getLong(key: String, defaultValue: Long): Preference<Long> = LongPrimitive(sharedPreferences, keyFlow, key, defaultValue)
|
||||
|
||||
override fun getInt(key: String, defaultValue: Int): Preference<Int> {
|
||||
return IntPrimitive(sharedPreferences, keyFlow, key, defaultValue)
|
||||
}
|
||||
override fun getInt(key: String, defaultValue: Int): Preference<Int> = IntPrimitive(sharedPreferences, keyFlow, key, defaultValue)
|
||||
|
||||
override fun getFloat(key: String, defaultValue: Float): Preference<Float> {
|
||||
return FloatPrimitive(sharedPreferences, keyFlow, key, defaultValue)
|
||||
}
|
||||
override fun getFloat(key: String, defaultValue: Float): Preference<Float> = FloatPrimitive(sharedPreferences, keyFlow, key, defaultValue)
|
||||
|
||||
override fun getBoolean(key: String, defaultValue: Boolean): Preference<Boolean> {
|
||||
return BooleanPrimitive(sharedPreferences, keyFlow, key, defaultValue)
|
||||
}
|
||||
override fun getBoolean(key: String, defaultValue: Boolean): Preference<Boolean> = BooleanPrimitive(sharedPreferences, keyFlow, key, defaultValue)
|
||||
|
||||
override fun getStringSet(key: String, defaultValue: Set<String>): Preference<Set<String>> {
|
||||
return StringSetPrimitive(sharedPreferences, keyFlow, key, defaultValue)
|
||||
}
|
||||
override fun getStringSet(key: String, defaultValue: Set<String>): Preference<Set<String>> = StringSetPrimitive(sharedPreferences, keyFlow, key, defaultValue)
|
||||
|
||||
override fun <T> getObject(
|
||||
key: String,
|
||||
defaultValue: T,
|
||||
serializer: (T) -> String,
|
||||
deserializer: (String) -> T,
|
||||
): Preference<T> {
|
||||
return Object(
|
||||
preferences = sharedPreferences,
|
||||
keyFlow = keyFlow,
|
||||
key = key,
|
||||
defaultValue = defaultValue,
|
||||
serializer = serializer,
|
||||
deserializer = deserializer,
|
||||
)
|
||||
}
|
||||
): Preference<T> = Object(
|
||||
preferences = sharedPreferences,
|
||||
keyFlow = keyFlow,
|
||||
key = key,
|
||||
defaultValue = defaultValue,
|
||||
serializer = serializer,
|
||||
deserializer = deserializer,
|
||||
)
|
||||
}
|
||||
|
||||
private val SharedPreferences.keyFlow
|
||||
|
||||
@@ -25,17 +25,15 @@ interface PreferenceStore {
|
||||
inline fun <reified T : Enum<T>> PreferenceStore.getEnum(
|
||||
key: String,
|
||||
defaultValue: T,
|
||||
): Preference<T> {
|
||||
return getObject(
|
||||
key = key,
|
||||
defaultValue = defaultValue,
|
||||
serializer = { it.name },
|
||||
deserializer = {
|
||||
try {
|
||||
enumValueOf(it)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
defaultValue
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
): Preference<T> = getObject(
|
||||
key = key,
|
||||
defaultValue = defaultValue,
|
||||
serializer = { it.name },
|
||||
deserializer = {
|
||||
try {
|
||||
enumValueOf(it)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
defaultValue
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -376,15 +376,13 @@ class ReaderActivity : EhActivity() {
|
||||
|
||||
private suspend fun makeToast(@StringRes resId: Int) = makeToast(getString(resId))
|
||||
|
||||
private fun provideImage(index: Int): Uri? {
|
||||
return AppConfig.externalTempDir?.let { dir ->
|
||||
mGalleryProvider?.saveToDir(index, dir.asUniFile())?.name?.let {
|
||||
FileProvider.getUriForFile(
|
||||
this,
|
||||
BuildConfig.APPLICATION_ID + ".fileprovider",
|
||||
File(dir, it),
|
||||
)
|
||||
}
|
||||
private fun provideImage(index: Int): Uri? = AppConfig.externalTempDir?.let { dir ->
|
||||
mGalleryProvider?.saveToDir(index, dir.asUniFile())?.name?.let {
|
||||
FileProvider.getUriForFile(
|
||||
this,
|
||||
BuildConfig.APPLICATION_ID + ".fileprovider",
|
||||
File(dir, it),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,28 +670,26 @@ class ReaderActivity : EhActivity() {
|
||||
*/
|
||||
private inner class ReaderConfig {
|
||||
|
||||
private fun getCombinedPaint(grayscale: Boolean, invertedColors: Boolean): Paint {
|
||||
return Paint().apply {
|
||||
colorFilter = ColorMatrixColorFilter(
|
||||
ColorMatrix().apply {
|
||||
if (grayscale) {
|
||||
setSaturation(0f)
|
||||
}
|
||||
if (invertedColors) {
|
||||
postConcat(
|
||||
ColorMatrix(
|
||||
floatArrayOf(
|
||||
-1f, 0f, 0f, 0f, 255f,
|
||||
0f, -1f, 0f, 0f, 255f,
|
||||
0f, 0f, -1f, 0f, 255f,
|
||||
0f, 0f, 0f, 1f, 0f,
|
||||
),
|
||||
private fun getCombinedPaint(grayscale: Boolean, invertedColors: Boolean): Paint = Paint().apply {
|
||||
colorFilter = ColorMatrixColorFilter(
|
||||
ColorMatrix().apply {
|
||||
if (grayscale) {
|
||||
setSaturation(0f)
|
||||
}
|
||||
if (invertedColors) {
|
||||
postConcat(
|
||||
ColorMatrix(
|
||||
floatArrayOf(
|
||||
-1f, 0f, 0f, 0f, 255f,
|
||||
0f, -1f, 0f, 0f, 255f,
|
||||
0f, 0f, -1f, 0f, 255f,
|
||||
0f, 0f, 0f, 1f, 0f,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -759,12 +755,10 @@ class ReaderActivity : EhActivity() {
|
||||
/**
|
||||
* Picks background color for [ReaderActivity] based on light/dark theme preference
|
||||
*/
|
||||
private fun automaticBackgroundColor(): Int {
|
||||
return if (baseContext.isNightMode()) {
|
||||
R.color.reader_background_dark
|
||||
} else {
|
||||
android.R.color.white
|
||||
}
|
||||
private fun automaticBackgroundColor(): Int = if (baseContext.isNightMode()) {
|
||||
R.color.reader_background_dark
|
||||
} else {
|
||||
android.R.color.white
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -50,15 +50,13 @@ enum class ReadingModeType(
|
||||
|
||||
fun fromSpinner(position: Int?) = entries.find { value -> value.prefValue == position } ?: DEFAULT
|
||||
|
||||
fun toViewer(preference: Int?, activity: ReaderActivity): BaseViewer {
|
||||
return when (fromPreference(preference)) {
|
||||
LEFT_TO_RIGHT -> L2RPagerViewer(activity)
|
||||
RIGHT_TO_LEFT -> R2LPagerViewer(activity)
|
||||
VERTICAL -> VerticalPagerViewer(activity)
|
||||
WEBTOON -> WebtoonViewer(activity)
|
||||
CONTINUOUS_VERTICAL -> WebtoonViewer(activity, isContinuous = false)
|
||||
DEFAULT -> L2RPagerViewer(activity)
|
||||
}
|
||||
fun toViewer(preference: Int?, activity: ReaderActivity): BaseViewer = when (fromPreference(preference)) {
|
||||
LEFT_TO_RIGHT -> L2RPagerViewer(activity)
|
||||
RIGHT_TO_LEFT -> R2LPagerViewer(activity)
|
||||
VERTICAL -> VerticalPagerViewer(activity)
|
||||
WEBTOON -> WebtoonViewer(activity)
|
||||
CONTINUOUS_VERTICAL -> WebtoonViewer(activity, isContinuous = false)
|
||||
DEFAULT -> L2RPagerViewer(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,9 +286,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
||||
this@ReaderPageImageView.onImageLoaded()
|
||||
}
|
||||
|
||||
private fun Int.getSystemScaledDuration(): Int {
|
||||
return (this * context.animatorDurationScale).toInt().coerceAtLeast(1)
|
||||
}
|
||||
private fun Int.getSystemScaledDuration(): Int = (this * context.animatorDurationScale).toInt().coerceAtLeast(1)
|
||||
|
||||
/**
|
||||
* All of the config except [zoomDuration] will only be used for non-animated image.
|
||||
|
||||
@@ -69,28 +69,24 @@ open class Pager(
|
||||
* Whether the given [ev] should be intercepted. Only used to prevent crashes when child
|
||||
* views manipulate [requestDisallowInterceptTouchEvent].
|
||||
*/
|
||||
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||
return try {
|
||||
super.onInterceptTouchEvent(ev)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
false
|
||||
}
|
||||
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean = try {
|
||||
super.onInterceptTouchEvent(ev)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
false
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a touch event. Only used to prevent crashes when child views manipulate
|
||||
* [requestDisallowInterceptTouchEvent].
|
||||
*/
|
||||
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||
return try {
|
||||
super.onTouchEvent(ev)
|
||||
} catch (e: NullPointerException) {
|
||||
false
|
||||
} catch (e: IndexOutOfBoundsException) {
|
||||
false
|
||||
} catch (e: IllegalArgumentException) {
|
||||
false
|
||||
}
|
||||
override fun onTouchEvent(ev: MotionEvent): Boolean = try {
|
||||
super.onTouchEvent(ev)
|
||||
} catch (e: NullPointerException) {
|
||||
false
|
||||
} catch (e: IndexOutOfBoundsException) {
|
||||
false
|
||||
} catch (e: IllegalArgumentException) {
|
||||
false
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -115,11 +115,9 @@ class PagerConfig(
|
||||
field = value.also { it.invertMode = this.tappingInverted }
|
||||
}
|
||||
|
||||
override fun defaultNavigation(): ViewerNavigation {
|
||||
return when (viewer) {
|
||||
is VerticalPagerViewer -> LNavigation()
|
||||
else -> RightAndLeftNavigation()
|
||||
}
|
||||
override fun defaultNavigation(): ViewerNavigation = when (viewer) {
|
||||
is VerticalPagerViewer -> LNavigation()
|
||||
else -> RightAndLeftNavigation()
|
||||
}
|
||||
|
||||
override fun updateNavigation(navigationMode: Int) {
|
||||
|
||||
@@ -24,7 +24,8 @@ class PagerPageHolder(
|
||||
readerThemedContext: Context,
|
||||
val viewer: PagerViewer,
|
||||
val page: ReaderPage,
|
||||
) : ReaderPageImageView(readerThemedContext), ViewPagerAdapter.PositionableView {
|
||||
) : ReaderPageImageView(readerThemedContext),
|
||||
ViewPagerAdapter.PositionableView {
|
||||
|
||||
/**
|
||||
* Item that identifies this view. Needed by the adapter to not recreate views.
|
||||
|
||||
@@ -146,9 +146,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
|
||||
/**
|
||||
* Returns the view this viewer uses.
|
||||
*/
|
||||
override fun getView(): View {
|
||||
return pager
|
||||
}
|
||||
override fun getView(): View = pager
|
||||
|
||||
/**
|
||||
* Returns the PagerPageHolder for the provided page
|
||||
|
||||
+1
-3
@@ -44,9 +44,7 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
||||
/**
|
||||
* Returns the amount of items of the adapter.
|
||||
*/
|
||||
override fun getCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
override fun getCount(): Int = items.size
|
||||
|
||||
/**
|
||||
* Creates a new view for the item at the given [position].
|
||||
|
||||
@@ -9,9 +9,7 @@ class L2RPagerViewer(activity: ReaderActivity) : PagerViewer(activity) {
|
||||
/**
|
||||
* Creates a new left to right pager.
|
||||
*/
|
||||
override fun createPager(): Pager {
|
||||
return Pager(activity)
|
||||
}
|
||||
override fun createPager(): Pager = Pager(activity)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,9 +19,7 @@ class R2LPagerViewer(activity: ReaderActivity) : PagerViewer(activity) {
|
||||
/**
|
||||
* Creates a new right to left pager.
|
||||
*/
|
||||
override fun createPager(): Pager {
|
||||
return Pager(activity)
|
||||
}
|
||||
override fun createPager(): Pager = Pager(activity)
|
||||
|
||||
/**
|
||||
* Moves to the next page. On a R2L pager the next page is the one at the left.
|
||||
@@ -47,7 +43,5 @@ class VerticalPagerViewer(activity: ReaderActivity) : PagerViewer(activity) {
|
||||
/**
|
||||
* Creates a new vertical pager.
|
||||
*/
|
||||
override fun createPager(): Pager {
|
||||
return Pager(activity, isHorizontal = false)
|
||||
}
|
||||
override fun createPager(): Pager = Pager(activity, isHorizontal = false)
|
||||
}
|
||||
@@ -42,9 +42,7 @@ class WebtoonAdapter(val viewer: WebtoonViewer) : RecyclerView.Adapter<RecyclerV
|
||||
/**
|
||||
* Returns the amount of items of the adapter.
|
||||
*/
|
||||
override fun getItemCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
/**
|
||||
* Creates a new view holder for an item with the given [viewType].
|
||||
|
||||
@@ -71,9 +71,7 @@ class WebtoonConfig(
|
||||
field = value.also { it.invertMode = tappingInverted }
|
||||
}
|
||||
|
||||
override fun defaultNavigation(): ViewerNavigation {
|
||||
return LNavigation()
|
||||
}
|
||||
override fun defaultNavigation(): ViewerNavigation = LNavigation()
|
||||
|
||||
override fun updateNavigation(navigationMode: Int) {
|
||||
this.navigator = when (navigationMode) {
|
||||
|
||||
@@ -63,17 +63,13 @@ class WebtoonFrame(context: Context) : FrameLayout(context) {
|
||||
* Fling listener used to delegate events to the recycler view.
|
||||
*/
|
||||
inner class FlingListener : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onDown(e: MotionEvent): Boolean {
|
||||
return true
|
||||
}
|
||||
override fun onDown(e: MotionEvent): Boolean = true
|
||||
|
||||
override fun onFling(
|
||||
e1: MotionEvent?,
|
||||
e2: MotionEvent,
|
||||
velocityX: Float,
|
||||
velocityY: Float,
|
||||
): Boolean {
|
||||
return recycler?.zoomFling(velocityX.toInt(), velocityY.toInt()) ?: false
|
||||
}
|
||||
): Boolean = recycler?.zoomFling(velocityX.toInt(), velocityY.toInt()) ?: false
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -28,9 +28,7 @@ class WebtoonLayoutManager(activity: ReaderActivity) : LinearLayoutManager(activ
|
||||
* Returns the custom extra layout space.
|
||||
*/
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun getExtraLayoutSpace(state: RecyclerView.State): Int {
|
||||
return extraLayoutSpace
|
||||
}
|
||||
override fun getExtraLayoutSpace(state: RecyclerView.State): Int = extraLayoutSpace
|
||||
|
||||
/**
|
||||
* Returns the position of the last item whose end side is visible on screen.
|
||||
|
||||
+1
-3
@@ -14,7 +14,5 @@ class WebtoonSubsamplingImageView @JvmOverloads constructor(
|
||||
attrs: AttributeSet? = null,
|
||||
) : SubsamplingScaleImageView(context, attrs) {
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean = false
|
||||
}
|
||||
@@ -132,16 +132,12 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
|
||||
frame.addView(recycler)
|
||||
}
|
||||
|
||||
override fun getViewForInsets(): View {
|
||||
return recycler
|
||||
}
|
||||
override fun getViewForInsets(): View = recycler
|
||||
|
||||
/**
|
||||
* Returns the view this viewer uses.
|
||||
*/
|
||||
override fun getView(): View {
|
||||
return frame
|
||||
}
|
||||
override fun getView(): View = frame
|
||||
|
||||
/**
|
||||
* Destroys this viewer. Called when leaving the reader or swapping viewers.
|
||||
@@ -265,9 +261,7 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
|
||||
* Called from the containing activity when a generic motion [event] is received. It should
|
||||
* return true if the event was handled, false otherwise.
|
||||
*/
|
||||
override fun handleGenericMotionEvent(event: MotionEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
override fun handleGenericMotionEvent(event: MotionEvent): Boolean = false
|
||||
|
||||
override fun refreshAdapter() {
|
||||
refreshAdapter(layoutManager.findLastEndVisibleItemPosition())
|
||||
|
||||
@@ -22,16 +22,12 @@ val Context.animatorDurationScale: Float
|
||||
|
||||
private const val TABLET_UI_REQUIRED_SCREEN_WIDTH_DP = 720
|
||||
|
||||
fun Configuration.isTabletUi(): Boolean {
|
||||
return smallestScreenWidthDp >= TABLET_UI_REQUIRED_SCREEN_WIDTH_DP
|
||||
}
|
||||
fun Configuration.isTabletUi(): Boolean = smallestScreenWidthDp >= TABLET_UI_REQUIRED_SCREEN_WIDTH_DP
|
||||
|
||||
/**
|
||||
* Returns true if current context is in night mode
|
||||
*/
|
||||
fun Context.isNightMode(): Boolean {
|
||||
return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||
}
|
||||
fun Context.isNightMode(): Boolean = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||
|
||||
/**
|
||||
* Creates night mode Context depending on reader theme/background
|
||||
|
||||
@@ -23,9 +23,7 @@ abstract class ViewPagerAdapter : PagerAdapter() {
|
||||
container.removeView(view)
|
||||
}
|
||||
|
||||
override fun isViewFromObject(view: View, obj: Any): Boolean {
|
||||
return view === obj
|
||||
}
|
||||
override fun isViewFromObject(view: View, obj: Any): Boolean = view === obj
|
||||
|
||||
interface PositionableView {
|
||||
val item: Any
|
||||
|
||||
@@ -3,11 +3,12 @@ package moe.tarsin.coroutines
|
||||
inline fun <T> MutableIterable<T>.filterInPlaceInline(predicate: (T) -> Boolean, predicateResultToRemove: Boolean): Boolean {
|
||||
var result = false
|
||||
with(iterator()) {
|
||||
while (hasNext())
|
||||
while (hasNext()) {
|
||||
if (predicate(next()) == predicateResultToRemove) {
|
||||
remove()
|
||||
result = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -31,8 +32,9 @@ private suspend inline fun <T> MutableList<T>.filterInPlaceInline(predicate: (T)
|
||||
writeIndex++
|
||||
}
|
||||
return if (writeIndex < size) {
|
||||
for (removeIndex in lastIndex downTo writeIndex)
|
||||
for (removeIndex in lastIndex downTo writeIndex) {
|
||||
removeAt(removeIndex)
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
||||
@@ -92,6 +92,8 @@ jsoup = "org.jsoup:jsoup:1.17.2"
|
||||
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
|
||||
|
||||
ktlint = "com.pinterest:ktlint:1.3.0"
|
||||
|
||||
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||
|
||||
logcat = "com.squareup.logcat:logcat:0.1"
|
||||
|
||||
Reference in new issue
Block a user