Kill Legacy TextView (#1786)
* Kill Legacy TextView * Fix textStyle * Fix color * Add breakToTextAndUrl * Impl breakToTextAndUrl * Fix OOB * Update GalleryCommentsScreen.kt * Fix bug * Fix color * Refine regex * Add undeline as well * Refine * Revert * Don't break if not show image --------- Co-authored-by: Dude so hot <djohn@fbi.gov>
This commit is contained in:
1 parent
22f31f2ebc
commit
62927cb6a8
8 files changed
+141
-249
No files matched your search
@@ -1,33 +0,0 @@
|
||||
package com.hippo.ehviewer.ui.legacy
|
||||
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.graphics.drawable.DrawableWrapper
|
||||
import android.text.Html
|
||||
import android.widget.TextView
|
||||
import coil3.asDrawable
|
||||
import coil3.imageLoader
|
||||
import coil3.request.crossfade
|
||||
import coil3.size.Size
|
||||
import com.hippo.ehviewer.ktbuilder.imageRequest
|
||||
|
||||
class CoilImageGetter(
|
||||
private val textView: TextView,
|
||||
) : Html.ImageGetter {
|
||||
override fun getDrawable(source: String) = object : DrawableWrapper(null) {}.apply {
|
||||
with(textView.context) {
|
||||
imageLoader.enqueue(
|
||||
imageRequest {
|
||||
data(source)
|
||||
crossfade(false)
|
||||
size(Size.ORIGINAL)
|
||||
target { drawable ->
|
||||
setDrawable(drawable.asDrawable(resources))
|
||||
if (drawable is Animatable) drawable.start()
|
||||
setBounds(0, 0, intrinsicWidth, intrinsicHeight)
|
||||
textView.text = textView.text
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.ui.legacy
|
||||
|
||||
import android.content.Context
|
||||
import android.text.Spanned
|
||||
import android.text.style.ClickableSpan
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
|
||||
class LinkifyTextView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
private val onCurrentSpanUpdate: (ClickableSpan?) -> Unit,
|
||||
) : AppCompatTextView(context, attrs) {
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
// Let the parent or grandparent of TextView to handles click aciton.
|
||||
// Otherwise click effect like ripple will not work, and if touch area
|
||||
// do not contain a url, the TextView will still get MotionEvent.
|
||||
// onTouchEven must be called with MotionEvent.ACTION_DOWN for each touch
|
||||
// action on it, so we analyze touched url here.
|
||||
if (event.action == MotionEvent.ACTION_DOWN) {
|
||||
onCurrentSpanUpdate(null)
|
||||
if (text is Spanned) {
|
||||
var x = event.x.toInt()
|
||||
var y = event.y.toInt()
|
||||
x -= totalPaddingLeft
|
||||
y -= totalPaddingTop
|
||||
x += scrollX
|
||||
y += scrollY
|
||||
val layout = layout
|
||||
if (null != layout) {
|
||||
val line = layout.getLineForVertical(y)
|
||||
val off = layout.getOffsetForHorizontal(line, x.toFloat())
|
||||
val spans = (text as Spanned).getSpans(off, off, ClickableSpan::class.java)
|
||||
if (spans.isNotEmpty()) {
|
||||
onCurrentSpanUpdate(spans[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,36 @@
|
||||
package com.hippo.ehviewer.ui.main
|
||||
|
||||
import android.text.style.ClickableSpan
|
||||
import android.text.style.URLSpan
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ProvideTextStyle
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import androidx.compose.ui.text.LinkInteractionListener
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextLinkStyles
|
||||
import androidx.compose.ui.text.fromHtml
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import arrow.core.Either
|
||||
import coil3.compose.AsyncImage
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.data.GalleryComment
|
||||
import com.hippo.ehviewer.ui.legacy.LinkifyTextView
|
||||
import com.hippo.ehviewer.ui.screen.breakToTextAndUrl
|
||||
import com.hippo.ehviewer.util.ReadableTime
|
||||
|
||||
typealias TextOrUrl = Either<String, AnnotatedString>
|
||||
typealias TextOrUrlList = List<TextOrUrl>
|
||||
|
||||
@Composable
|
||||
fun GalleryCommentCard(
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -34,22 +38,14 @@ fun GalleryCommentCard(
|
||||
onCardClick: () -> Unit,
|
||||
onUserClick: () -> Unit,
|
||||
onUrlClick: (String) -> Unit,
|
||||
update: LinkifyTextView.() -> Unit,
|
||||
) = comment.run {
|
||||
var currentSpan by remember(comment) { mutableStateOf<ClickableSpan?>(null) }
|
||||
Card(
|
||||
onClick = {
|
||||
val span = currentSpan.apply { currentSpan = null }
|
||||
if (span is URLSpan) {
|
||||
onUrlClick(span.url)
|
||||
} else {
|
||||
onCardClick()
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
) {
|
||||
val keylineMargin = dimensionResource(id = R.dimen.keyline_margin)
|
||||
ConstraintLayout(modifier = Modifier.padding(horizontal = keylineMargin, vertical = 8.dp).fillMaxWidth()) {
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
overflow: TextOverflow = TextOverflow.Clip,
|
||||
showImage: Boolean = false,
|
||||
processComment: @Composable (GalleryComment, TextLinkStyles, LinkInteractionListener) -> AnnotatedString = { c, s, l -> AnnotatedString.fromHtml(c.comment, s, l) },
|
||||
) = with(comment) {
|
||||
Card(onClick = onCardClick, modifier = modifier) {
|
||||
val margin = dimensionResource(id = R.dimen.keyline_margin)
|
||||
ConstraintLayout(modifier = Modifier.padding(horizontal = margin, vertical = 8.dp).fillMaxWidth()) {
|
||||
val (userRef, timeRef, commentRef) = createRefs()
|
||||
ProvideTextStyle(value = MaterialTheme.typography.titleSmall) {
|
||||
val userText = if (uploader) stringResource(id = R.string.comment_user_uploader, user.orEmpty()) else user.orEmpty()
|
||||
@@ -68,21 +64,41 @@ fun GalleryCommentCard(
|
||||
},
|
||||
)
|
||||
}
|
||||
val textColor = LocalContentColor.current.toArgb()
|
||||
val linkTextColor = MaterialTheme.colorScheme.primary.toArgb()
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
LinkifyTextView(context) { currentSpan = it }.apply {
|
||||
setTextColor(textColor)
|
||||
setLinkTextColor(linkTextColor)
|
||||
}
|
||||
},
|
||||
Column(
|
||||
modifier = Modifier.constrainAs(commentRef) {
|
||||
start.linkTo(parent.start)
|
||||
top.linkTo(userRef.bottom, margin = 8.dp)
|
||||
},
|
||||
update = update,
|
||||
)
|
||||
) {
|
||||
ProvideTextStyle(value = MaterialTheme.typography.bodyMedium) {
|
||||
val linkColor = MaterialTheme.colorScheme.primary
|
||||
val processed = processComment(
|
||||
comment,
|
||||
TextLinkStyles(style = SpanStyle(color = linkColor)),
|
||||
) { link ->
|
||||
check(link is LinkAnnotation.Url)
|
||||
onUrlClick(link.url)
|
||||
}
|
||||
if (showImage) {
|
||||
val list = breakToTextAndUrl(comment.comment, processed)
|
||||
list.forEach {
|
||||
when (it) {
|
||||
is Either.Left -> AsyncImage(
|
||||
model = it.value,
|
||||
contentDescription = null,
|
||||
)
|
||||
is Either.Right -> Text(text = it.value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = processed,
|
||||
maxLines = maxLines,
|
||||
overflow = overflow,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,5 @@
|
||||
package com.hippo.ehviewer.ui.screen
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.RelativeSizeSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.widget.TextView
|
||||
import androidx.compose.animation.AnimatedVisibilityScope
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -67,14 +60,23 @@ import androidx.compose.ui.platform.LocalTextToolbar
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import androidx.compose.ui.text.LinkInteractionListener
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextLinkStyles
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.fromHtml
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.em
|
||||
import androidx.compose.ui.unit.max
|
||||
import androidx.compose.ui.util.lerp
|
||||
import androidx.core.text.inSpans
|
||||
import androidx.core.text.parseAsHtml
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
@@ -87,8 +89,9 @@ import com.hippo.ehviewer.dao.Filter
|
||||
import com.hippo.ehviewer.dao.FilterMode
|
||||
import com.hippo.ehviewer.ui.composing
|
||||
import com.hippo.ehviewer.ui.jumpToReaderByPage
|
||||
import com.hippo.ehviewer.ui.legacy.CoilImageGetter
|
||||
import com.hippo.ehviewer.ui.main.GalleryCommentCard
|
||||
import com.hippo.ehviewer.ui.main.TextOrUrl
|
||||
import com.hippo.ehviewer.ui.main.TextOrUrlList
|
||||
import com.hippo.ehviewer.ui.openBrowser
|
||||
import com.hippo.ehviewer.ui.tools.animateFloatMergePredictiveBackAsState
|
||||
import com.hippo.ehviewer.ui.tools.normalizeSpan
|
||||
@@ -98,7 +101,6 @@ import com.hippo.ehviewer.ui.tools.thenIf
|
||||
import com.hippo.ehviewer.ui.tools.toBBCode
|
||||
import com.hippo.ehviewer.ui.tools.updateSpan
|
||||
import com.hippo.ehviewer.util.ReadableTime
|
||||
import com.hippo.ehviewer.util.TextUrl
|
||||
import com.hippo.ehviewer.util.addTextToClipboard
|
||||
import com.hippo.ehviewer.util.displayString
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
@@ -108,41 +110,68 @@ import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.sequences.forEach
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import rikka.core.res.resolveColor
|
||||
|
||||
private fun Context.generateComment(
|
||||
textView: TextView,
|
||||
private const val IMAGE_OBJ = ''
|
||||
private val IMAGE_PATTERN = Regex("<img src=\"([^\"]+)\"")
|
||||
private val URL_PATTERN = Regex("(http|https)://[a-z0-9A-Z%-]+(\\.[a-z0-9A-Z%-]+)+(:\\d{1,5})?(/[a-zA-Z0-9-_~:#@!&',;=%/*.?+$\\[\\]()]+)?/?")
|
||||
|
||||
fun breakToTextAndUrl(origin: String, text: AnnotatedString): TextOrUrlList {
|
||||
val urls = IMAGE_PATTERN.findAll(origin).toList()
|
||||
if (urls.isEmpty()) return listOf(text.right())
|
||||
val iter = urls.iterator()
|
||||
var currentOfs = 0
|
||||
return buildList<TextOrUrl> {
|
||||
while (true) {
|
||||
val index = text.text.indexOf(IMAGE_OBJ, currentOfs)
|
||||
if (index == -1) break
|
||||
add(text.subSequence(currentOfs, index).right())
|
||||
add(iter.next().groupValues[1].left())
|
||||
currentOfs = index + 1
|
||||
}
|
||||
add(text.subSequence(currentOfs, text.length).right())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun processComment(
|
||||
comment: GalleryComment,
|
||||
): CharSequence {
|
||||
val sp = comment.comment.parseAsHtml(imageGetter = CoilImageGetter(textView))
|
||||
val ssb = SpannableStringBuilder(sp)
|
||||
if (0L != comment.id && 0 != comment.score) {
|
||||
val score = comment.score
|
||||
val scoreString = if (score > 0) "+$score" else score.toString()
|
||||
ssb.append(" ").inSpans(
|
||||
RelativeSizeSpan(0.8f),
|
||||
StyleSpan(Typeface.BOLD),
|
||||
ForegroundColorSpan(theme.resolveColor(android.R.attr.textColorSecondary)),
|
||||
) {
|
||||
append(scoreString)
|
||||
linkStyle: TextLinkStyles,
|
||||
onLinkClick: LinkInteractionListener,
|
||||
) = AnnotatedString.fromHtml(comment.comment, linkStyle, onLinkClick).let { text ->
|
||||
buildAnnotatedString {
|
||||
val onSurfaceVariant = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
append(text)
|
||||
URL_PATTERN.findAll(text).forEach { result ->
|
||||
val start = result.range.first
|
||||
val end = result.range.last + 1
|
||||
if (!text.hasLinkAnnotations(start, end)) {
|
||||
addLink(LinkAnnotation.Url(result.groupValues[0], linkStyle, onLinkClick), start, end)
|
||||
}
|
||||
}
|
||||
val style = SpanStyle(fontSize = 0.8f.em, fontWeight = FontWeight.Bold, color = onSurfaceVariant)
|
||||
if (comment.id != 0L && comment.score != 0) {
|
||||
val score = comment.score
|
||||
val scoreString = if (score > 0) "+$score" else score.toString()
|
||||
append(" ")
|
||||
withStyle(style) {
|
||||
append(scoreString)
|
||||
}
|
||||
}
|
||||
if (comment.lastEdited != 0L) {
|
||||
append("\n\n")
|
||||
withStyle(style) {
|
||||
append(
|
||||
stringResource(
|
||||
R.string.last_edited,
|
||||
ReadableTime.getTimeAgo(comment.lastEdited),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (comment.lastEdited != 0L) {
|
||||
val str = getString(
|
||||
R.string.last_edited,
|
||||
ReadableTime.getTimeAgo(comment.lastEdited),
|
||||
)
|
||||
ssb.append("\n\n").inSpans(
|
||||
RelativeSizeSpan(0.8f),
|
||||
StyleSpan(Typeface.BOLD),
|
||||
ForegroundColorSpan(theme.resolveColor(android.R.attr.textColorSecondary)),
|
||||
) {
|
||||
append(str)
|
||||
}
|
||||
}
|
||||
return TextUrl.handleTextUrl(ssb)
|
||||
}
|
||||
|
||||
private val MinimumContentPaddingEditText = 88.dp
|
||||
@@ -380,9 +409,9 @@ fun AnimatedVisibilityScope.GalleryCommentsScreen(gid: Long, navigator: Destinat
|
||||
},
|
||||
onCardClick = { launch { doCommentAction(item) } },
|
||||
onUrlClick = { if (!jumpToReaderByPage(it, galleryDetail)) if (!navWithUrl(it)) openBrowser(it) },
|
||||
) {
|
||||
text = generateComment(this, item)
|
||||
}
|
||||
processComment = { c, s, l -> processComment(c, s, l) },
|
||||
showImage = true,
|
||||
)
|
||||
}
|
||||
if (comments.hasMore) {
|
||||
item {
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.hippo.ehviewer.ui.screen
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.text.TextUtils.TruncateAt.END
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.SharedTransitionScope
|
||||
import androidx.compose.foundation.MutatorMutex
|
||||
@@ -52,9 +51,9 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.LocalPinnableContainer
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
@@ -101,7 +100,6 @@ import com.hippo.ehviewer.ui.confirmRemoveDownload
|
||||
import com.hippo.ehviewer.ui.destinations.GalleryCommentsScreenDestination
|
||||
import com.hippo.ehviewer.ui.getFavoriteIcon
|
||||
import com.hippo.ehviewer.ui.jumpToReaderByPage
|
||||
import com.hippo.ehviewer.ui.legacy.CoilImageGetter
|
||||
import com.hippo.ehviewer.ui.main.EhPreviewItem
|
||||
import com.hippo.ehviewer.ui.main.GalleryCommentCard
|
||||
import com.hippo.ehviewer.ui.main.GalleryDetailErrorTip
|
||||
@@ -411,11 +409,10 @@ fun BelowHeader(galleryDetail: GalleryDetail) {
|
||||
onCardClick = ::onNavigateToCommentScene,
|
||||
onUserClick = ::onNavigateToCommentScene,
|
||||
onUrlClick = { if (!jumpToReaderByPage(it, galleryDetail)) if (!navWithUrl(it)) openBrowser(it) },
|
||||
) {
|
||||
maxLines = 5
|
||||
ellipsize = END
|
||||
text = item.comment.parseAsHtml(imageGetter = CoilImageGetter(this))
|
||||
}
|
||||
maxLines = 5,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
showImage = false,
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.util
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.hippo.ehviewer.R
|
||||
import java.util.Locale
|
||||
import kotlinx.datetime.Clock
|
||||
@@ -95,38 +98,28 @@ object ReadableTime {
|
||||
}
|
||||
private val resources = appCtx.resources
|
||||
|
||||
@Composable
|
||||
fun getTimeAgo(time: Long): String {
|
||||
val nowInstant = Clock.System.now()
|
||||
val now = nowInstant.toEpochMilliseconds()
|
||||
val diff = now - time
|
||||
return when {
|
||||
(diff < 0 || time <= 0) -> {
|
||||
resources.getString(R.string.from_the_future)
|
||||
}
|
||||
diff < MINUTE_MILLIS -> {
|
||||
resources.getString(R.string.just_now)
|
||||
}
|
||||
diff < 2 * MINUTE_MILLIS -> {
|
||||
resources.getQuantityString(R.plurals.some_minutes_ago, 1, 1)
|
||||
}
|
||||
(diff < 0 || time <= 0) -> stringResource(id = R.string.from_the_future)
|
||||
diff < MINUTE_MILLIS -> stringResource(id = R.string.just_now)
|
||||
diff < 2 * MINUTE_MILLIS -> pluralStringResource(R.plurals.some_minutes_ago, 1, 1)
|
||||
diff < 50 * MINUTE_MILLIS -> {
|
||||
val minutes = (diff / MINUTE_MILLIS).toInt()
|
||||
resources.getQuantityString(R.plurals.some_minutes_ago, minutes, minutes)
|
||||
}
|
||||
diff < 90 * MINUTE_MILLIS -> {
|
||||
resources.getQuantityString(R.plurals.some_hours_ago, 1, 1)
|
||||
pluralStringResource(R.plurals.some_minutes_ago, minutes, minutes)
|
||||
}
|
||||
diff < 90 * MINUTE_MILLIS -> pluralStringResource(R.plurals.some_hours_ago, 1, 1)
|
||||
diff < 24 * HOUR_MILLIS -> {
|
||||
val hours = (diff / HOUR_MILLIS).toInt()
|
||||
resources.getQuantityString(R.plurals.some_hours_ago, hours, hours)
|
||||
pluralStringResource(R.plurals.some_hours_ago, hours, hours)
|
||||
}
|
||||
diff < 48 * HOUR_MILLIS -> {
|
||||
resources.getString(R.string.yesterday)
|
||||
}
|
||||
diff < WEEK_MILLIS -> {
|
||||
val days = (diff / DAY_MILLIS).toInt()
|
||||
resources.getString(R.string.some_days_ago, days)
|
||||
stringResource(id = R.string.yesterday)
|
||||
}
|
||||
diff < WEEK_MILLIS -> stringResource(R.string.some_days_ago, (diff / DAY_MILLIS).toInt())
|
||||
else -> {
|
||||
val timeZone = TimeZone.currentSystemDefault()
|
||||
val nowDate = nowInstant.toLocalDateTime(timeZone).date
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.util
|
||||
|
||||
import android.text.Spannable
|
||||
import android.text.style.URLSpan
|
||||
|
||||
object TextUrl {
|
||||
private val URL_PATTERN =
|
||||
Regex("(http|https)://[a-z0-9A-Z%-]+(\\.[a-z0-9A-Z%-]+)+(:\\d{1,5})?(/[a-zA-Z0-9-_~:#@!&',;=%/*.?+$\\[\\]()]+)?/?")
|
||||
|
||||
fun handleTextUrl(content: Spannable): Spannable {
|
||||
URL_PATTERN.findAll(content).forEach { result ->
|
||||
val start = result.range.first
|
||||
val end = result.range.last + 1
|
||||
val links = content.getSpans(start, end, URLSpan::class.java)
|
||||
if (links.isNotEmpty()) {
|
||||
// There has been URLSpan already, leave it alone
|
||||
return@forEach
|
||||
}
|
||||
val urlSpan = URLSpan(result.groupValues[0])
|
||||
content.setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
return content
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package rikka.core.res
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.ColorInt
|
||||
|
||||
@ColorInt
|
||||
fun Resources.Theme.resolveColor(@AttrRes attrId: Int): Int {
|
||||
val a = obtainStyledAttributes(intArrayOf(attrId))
|
||||
val res = a.getColor(0, 0)
|
||||
a.recycle()
|
||||
return res
|
||||
}
|
||||
Reference in new issue
Block a user